From 1b536410a8cdea2f887480c96149ac181a5c4159 Mon Sep 17 00:00:00 2001 From: Stephen Shkardoon Date: Tue, 8 Oct 2019 23:57:15 +1300 Subject: Add crack-qr-uri script Uses the CPU to try every possible password to decode a QR code. --- README.md | 16 ++++++++ crack-qr-uri.go | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 crack-qr-uri.go diff --git a/README.md b/README.md index 3bbdd0e..30084ac 100644 --- a/README.md +++ b/README.md @@ -67,3 +67,19 @@ real 0m1.212s user 0m1.209s sys 0m0.003s ``` + +# crack-qr-uri.go +The QR code normally comes with a relatively weak password, along with a MAC that can verify the password. This allows us to perform a bruteforce of all possible passwords in a relatively short period, even with a CPU implementation. Simply run the script with the QR code URI as a parameter and it will discover the password. + +Performance on with a single modern CPU core results in 0.720 seconds (approximately, of course) to perform 1000 password attempts. The keyspace exists from 0 to 99999999. + +Example (AWS EC2 c5.metal instance - 96 cores): +``` +$ time go run crack-qr-uri.go -uri 'igmobileotp://?action=secactivate&enc=VRUq6IoLWQRCMRITZEHtHUSWJiPwgu%2FN1BFyUHE5kxuHIEYoE3zmNTrAHeeUM5S3gzCnTy%2F%2Bdnbu%2FsjjQW%2BNEISx8C4ra8rLpxOl8E8w4KXHgjeBRgdvSzl%2BbzX5RYRrQlWgK8hsBT4pQYE0eFgW2TmRbzXu1Mu7XjKDcwsJLew32jQC2qyPLP8hljnv2rHwwsMfhQwgJUJYfctwLWWEDUFukEckaZ4O&v=1&mac=mhVL8BWKaishMa5%2B' -threads 95 +action=secactivate&enc=VRUq6IoLWQRCMRITZEHtHUSWJiPwgu%2FN1BFyUHE5kxuHIEYoE3zmNTrAHeeUM5S3gzCnTy%2F%2Bdnbu%2FsjjQW%2BNEISx8C4ra8rLpxOl8E8w4KXHgjeBRgdvSzl%2BbzX5RYRrQlWgK8hsBT4pQYE0eFgW2TmRbzXu1Mu7XjKDcwsJLew32jQC2qyPLP8hljnv2rHwwsMfhQwgJUJYfctwLWWEDUFukEckaZ4O&v=1 +Candidate password found: 54998317 + +real 67m23.690s +user 3047m42.788s +sys 870m1.228s +``` diff --git a/crack-qr-uri.go b/crack-qr-uri.go new file mode 100644 index 0000000..f222ecf --- /dev/null +++ b/crack-qr-uri.go @@ -0,0 +1,115 @@ +package main + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/base64" + "flag" + "fmt" + "golang.org/x/crypto/pbkdf2" + "net/url" + "os" + "reflect" + "strconv" + "strings" +) + +func main() { + var uri = flag.String("uri", "", "Example: igmobileotp://?action=secactivate&enc=VRUq6IoLWQRCMRITZEHtHUSWJiPwgu%2FN1BFyUHE5kxuHIEYoE3zmNTrAHeeUM5S3gzCnTy%2F%2Bdnbu%2FsjjQW%2BNEISx8C4ra8rLpxOl8E8w4KXHgjeBRgdvSzl%2BbzX5RYRrQlWgK8hsBT4pQYE0eFgW2TmRbzXu1Mu7XjKDcwsJLew32jQC2qyPLP8hljnv2rHwwsMfhQwgJUJYfctwLWWEDUFukEckaZ4O&v=1&mac=mhVL8BWKaishMa5%2B") + var threadsPtr = flag.Int("threads", 4, "Number of threads to use. Set to ncores, or ncores - 1. Load is 100% CPU") + + flag.Parse() + + threads := *threadsPtr + + if *uri == "" { + panic("Specify a URI") + } + + obj, err := url.Parse(*uri) + if err != nil { + panic(err) + } + + if obj.Scheme != "igmobileotp" { + fmt.Println("Only the scheme igmobileotp is currently supported") + } + + // Parse the query string component + q := obj.Query() + + if (len(q["action"]) != 1) || (q["action"][0] != "secactivate") { + fmt.Println("Only the secactivate action is currently supported") + } + + // Validate the encrypted data really exists + if len(q["enc"]) != 1 { + panic("No enc provided") + } + + // Validate we have a MAC that we can verify decryption with + if len(q["mac"]) != 1 { + panic("No mac provided") + } + mac, err := base64.StdEncoding.DecodeString(q["mac"][0]) + if err != nil { + panic(err) + } + + // Decode the enc paramater + enc, err := base64.StdEncoding.DecodeString(q["enc"][0]) + if err != nil { + panic(err) + } + + // Extract out the payload that is used for HMAC validation + from := strings.Index(*uri, "?") + 1 // 1 more, because the ? + to := strings.LastIndex(*uri, "&") + hmacPayload := (*uri)[from:to] + + fmt.Println(hmacPayload) + + // Farm the work out to threads + passwords := make(chan []byte) + doner := make(chan bool, threads) + + for i := 0; i < threads; i++ { + go checkPassword(enc[0:8], []byte(hmacPayload), mac, passwords, doner) + } + + //for i := 54998317; i < 54998318; i++ { + for i := 0; i < 99999999; i++ { + passwords <- []byte(strconv.Itoa(i)) + } + + close(passwords) + + for i := 0; i < threads; i++ { + <-doner + } + + fmt.Println("Password was not found. Is your URI corrupted?") + os.Exit(1) + +} + +func checkPassword(salt []byte, macedPayload []byte, macValue []byte, passwords <-chan []byte, doner chan<- bool) { + for password := range passwords { + dk := pbkdf2.Key(password, salt, 1000, 64, sha256.New) + + // Validate whether the key is correct with a HMAC verification + hmacKey := dk[16:48] + + macer := hmac.New(sha256.New, hmacKey) + macer.Write(macedPayload) + calculatedMac := macer.Sum(nil) + + if reflect.DeepEqual(calculatedMac[0:12], macValue) { + // Success! + fmt.Println("Password found: ", string(password)) + os.Exit(0) + } + } + + doner <- true +} -- cgit v1.2.3