Skip to content

Instantly share code, notes, and snippets.

@gravilk
Last active April 25, 2024 03:19
Show Gist options
  • Save gravilk/1299aefa9324e33d1d84e0ad23b6f3ad to your computer and use it in GitHub Desktop.
Save gravilk/1299aefa9324e33d1d84e0ad23b6f3ad to your computer and use it in GitHub Desktop.
ProtonMail captcha solving function
package main
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"image"
_ "image/png"
"math"
"net/http"
"regexp"
"strconv"
)
func solveImage(imageData []byte) int {
image, _, err := image.Decode(bytes.NewReader(imageData))
var combo int
var last int
if err != nil {
fmt.Println(err)
}
for i := 0; i < image.Bounds().Max.Y; i++ {
clr := image.At(image.Bounds().Min.X, i)
r, _, _, _ := clr.RGBA()
if last == int(r>>8) && int(r>>8) < 200 {
combo++
} else {
combo = 0
}
if combo > 10 {
return i - combo
}
last = int(r >> 8)
}
return -1
}
func readResponse(response *http.Response) ([]byte, error) {
var reader io.ReadCloser
switch response.Header.Get("Content-Encoding") {
case "gzip":
reader, _ = gzip.NewReader(response.Body)
defer reader.Close()
default:
reader = response.Body
}
return io.ReadAll(reader)
}
func makeRequest(method string, url string, body []byte, headers map[string]string) *http.Request {
req, _ := http.NewRequest(method, url, bytes.NewReader(body))
for key, value := range headers {
req.Header.Set(key, value)
}
req.Header.Set("accept-encoding", "gzip, deflate, br")
return req
}
func solveChallenge(challenge string) int {
var curr int
leadingZeros := 13
for {
input := strconv.Itoa(curr) + challenge
sha := sha256.New()
sha.Write([]byte(input))
shaResult := sha.Sum(nil)
hexResult := hex.EncodeToString(shaResult)
j := (leadingZeros + 3) / 4
l, _ := strconv.ParseInt(hexResult[:j], 16, 32)
if float64(l) < math.Pow(2, float64(4*j-leadingZeros)) {
return curr
} else {
curr++
}
}
}
func solvePOW(challenges []string) []int {
var solved []int
for _, challenge := range challenges {
res := solveChallenge(challenge)
solved = append(solved, res)
}
return solved
}
var tokenRegex = regexp.MustCompile(`sendToken\(\'(?P<T1>.*?)\'\+\'(?P<T2>.*?)\'`)
var t1 = tokenRegex.SubexpIndex("T1")
var t2 = tokenRegex.SubexpIndex("T2")
type CaptchaResp struct {
Status string `json:"status"`
Token string `json:"token"`
Challenges []string `json:"challenges"`
LeadingZerosRequired int `json:"nLeadingZerosRequired"`
ContestID string `json:"contestId"`
}
type CaptchaSubmitReq struct {
CorrectY int `json:"y"`
Answers []int `json:"answers"`
ClientData *string `json:"clientData"`
PieceLoadElapsedMs int `json:"pieceLoadElapsedMs"`
BgLoadElapsedMs int `json:"bgLoadElapsedMs"`
ChallengeLoadElapsedMs int `json:"challengeLoadElapsedMs"`
SolveChallengeMs int `json:"solveChallengeMs"`
PowElapsedMs int `json:"powElapsedMs"`
}
func solveCaptcha(client *http.Client, captchaToken string) string {
var captchaData CaptchaResp
specialTokenReq := makeRequest("GET", fmt.Sprintf("https://account-api.proton.me/core/v4/captcha?Token=%s&ForceWebMessaging=1", captchaToken), nil, map[string]string{})
sResp, _ := client.Do(specialTokenReq)
sBody, _ := readResponse(sResp)
foundTokens := tokenRegex.FindStringSubmatch(string(sBody))
tkn := foundTokens[t1] + foundTokens[t2]
url := fmt.Sprintf("https://account-api.proton.me/captcha/v1/api/init?challengeType=1D&parentURL=https://account-api.proton.me/core/v4/captcha?Token=%s&ForceWebMessaging=1&displayedLang=en&supportedLangs=en-US,en-US,en,en-US,en&purpose=signup", captchaToken)
resp, _ := http.Get(url)
body, _ := readResponse(resp)
json.Unmarshal(body, &captchaData)
resp, _ = http.Get(fmt.Sprintf("https://account-api.proton.me/captcha/v1/api/bg?token=%s", captchaData.Token))
body, _ = readResponse(resp)
correctY := solveImage(body)
powSolution := solvePOW(captchaData.Challenges)
captchaReq := CaptchaSubmitReq{
CorrectY: correctY,
Answers: powSolution,
PieceLoadElapsedMs: 140,
BgLoadElapsedMs: 180,
ChallengeLoadElapsedMs: 180,
ClientData: nil,
SolveChallengeMs: 5000,
PowElapsedMs: 540,
}
stringified, _ := json.Marshal(captchaReq)
finalRequest := makeRequest("GET", fmt.Sprintf("https://account-api.proton.me/captcha/v1/api/validate?token=%s&contestId=%s&purpose=signup", captchaData.Token, captchaData.ContestID), nil, map[string]string{
"pcaptcha": string(stringified),
"accept": "*/*",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36",
"accept-language": "pl-PL,pl;q=0.9,en-US;q=0.8,en;q=0.7",
})
client.Do(finalRequest)
return captchaToken + ":" + tkn + captchaData.Token
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment