C# ile ALTCHA Nasıl Çözülür ve Token Değiştirilmeden Geri Gönderilir

solve altcha in c# - How to Solve ALTCHA in C# and Post the Token Back Unchanged

C# ile ALTCHA çözmek için okunacak hiçbir şey yok. ALTCHA bir tanıma yöntemi değil, iş kanıtı (proof of work) yöntemidir: site bir challenge dağıtır ve istemcinin bu challenge’ı sağlayan sayıyı kaba kuvvetle bulması gerekir. Ortada görüntü, ses ya da tahmin yoktur; bu da çözümü deterministik ve hızlı kılar. Ya cevabı bulur ya da challenge bozuktur veya süresi çoktan geçmiştir. CapSkip, ALTCHA desteğini 1.2.6 sürümünde ekledi ve .NET SDK bunu, sayfa URL’si ile challenge’ı alan tek bir metot olarak sunuyor. İnsanları gerçekten zorlayan kısım ise sonrasında olanlar: token, çözücünün döndürdüğü hâliyle birebir aynı şekilde forma girmelidir.

Neye ihtiyacınız var

  • Bir Windows makinesinde çalışan CapSkip 1.2.6 veya üzeri. ALTCHA desteği bu sürümle geldi.
  • CapSkip .NET paketi. Bu paket .NET Standard 2.0’ı hedefler; yani .NET Framework 4.6.1 ve üzeri, .NET Core 2.0 ve üzeri ve .NET 6 ile sonrası desteklenir.
  • Widget’ın bulunduğu sayfanın URL’si ve o widget’ın challenge’ını çektiği endpoint.
  • Çözücü için bir adres. Local modu 127.0.0.1 üzerinden yalnızca o cihaza yanıt verir; Server modu ise ağ adresinizi veya genel IP’nizi dinler, böylece başka bir makine ona ulaşabilir. Hangisini istediğinizi 4. adım anlatıyor; ikisi de şurada bulunur: bağlantı ayarları.
# dotnet add package CapSkip
dotnet add package CapSkip

1. adım: ALTCHA widget’ının çağırdığı endpoint’i bulun

Geri kalan her şey bu tek değere bağlı, bu yüzden önce onu alın. DevTools’u açın, Network sekmesine geçin ve widget’ın bulunduğu sayfayı yeniden yükleyin. Widget, challenge’ı için genellikle içinde altcha geçen bir yola istek gönderir. Çözücüye verdiğiniz şey bu istek URL’sidir; o endpoint’in döndürdüğü JSON ise challenge belgesinin kendisidir ve URL yerine bu belgeyi de verebilirsiniz.

Challenge’ı adlandıran özniteliği tahmin etmeye çalışmayın, çünkü bu öznitelik widget nesilleri arasında değişti. Sayfa kaynağını okuyun.

Widget nesliChallenge’ı adlandıran öznitelik
v1 ve v2Bir endpoint için challengeurl, satır içi challenge için ayrı bir challengejson özniteliği
v3 ve sonrasıchallenge; aynı öznitelik hem bir URL hem de challenge verisini kabul eder

Üç görüntüleme stili olan native, checkbox ve switch tamamen görseldir. Üçü de aynı payload’ı gönderir ve aradaki fark asla çözücüye ulaşmaz, bu yüzden hangisine baktığınızı çözmek zorunda değilsiniz. ALTCHA, widget özniteliklerini şurada ele alıyor: kendi entegrasyon dokümanları.

2. adım: çözüm çağrısı ve challenge vermenin iki yolu

Tek metot, iki argüman: önce sayfa URL’si, ardından challenge’ı taşıyan bir seçenekler sözlüğü. Ona endpoint’i verin, CapSkip challenge’ı sizin için çeker.

// dotnet add package CapSkip
using CapSkip;

var solver = new CapSkipClient(host: "127.0.0.1", port: 8080);

// CapSkip fetches the challenge, then brute-forces the counter.
var result = await solver.AltchaAsync(
    "https://example.com/signup",
    new Dictionary<string, object?>
    {
        ["challenge_url"] = "https://example.com/altcha/challenge",
    });

Console.WriteLine(result.Token);   // base64 payload for the form field
Console.WriteLine(result.Number);  // the counter that satisfied it

Sonuçtaki iki alan yalnızca ALTCHA içindir. Token, formun beklediği base64 payload’ıdır; Number ise challenge’ı çözen sayaçtır. Code özelliği Token ile aynı dizeyi taşır, yani ikisi de işe yarar; ancak Token, gireceği alanın adını taşıdığı için çağrı yerinde daha iyi okunur. GeeTest alanları ve Turnstile user agent değeri burada null kalır.

Number’ı loglamaya değer. Payload’ları farklı olsa da bu değer her iki ALTCHA nesli için de raporlanır: legacy bir token sayacı en üst düzeyde taşır, proof-of-work v2 token’ı ise taşımaz ve sayacı bir solution nesnesinin içinde tutar. CapSkip bu değeri kendi API yanıtında solution nesnesinden okur ve bu yanıt her iki nesli de aynı şekilde raporlar.

Bunun yerine challenge belgesini vermek

Kodunuz challenge’ı zaten çektiyse belgeyi verin; o zaman hiç ağ isteği yapılmaz. Sayfayı hâlihazırda scrape ediyorsanız bu daha hızlı yoldur ve challenge bir endpoint’ten gelmek yerine HTML içine gömülü geliyorsa kullanmanız gereken yol budur.

// No fetch happens: the document is already here.
var result = await solver.AltchaAsync(
    "https://example.com/signup",
    new Dictionary<string, object?>
    {
        ["challenge_json"] = new Dictionary<string, object>
        {
            ["algorithm"] = "SHA-256",
            ["challenge"] = "YOUR_CHALLENGE_HASH",
            ["salt"] = "YOUR_SALT",
            ["signature"] = "YOUR_SIGNATURE",
            ["maxnumber"] = 1000000,
        },
    });

Bu seçenek bir sözlük alır ve sözlüğü sizin için serileştirir; elinizde hazır bir JSON dizesi varsa onu da verebilirsiniz. Hem endpoint’i hem belgeyi göndermek serbesttir ve satır içi belge kazanır, çünkü çekme işlemi yalnızca sizin az önce verdiğiniz şeyi yeniden almak olurdu. İki yol yük altında da farklı davranır: süresi çoktan geçmiş bir satır içi challenge, boşuna hash alınmak yerine doğrudan reddedilir; buna karşılık bir endpoint, iş kuyrukta beklerken ilk challenge öldüyse çözücünün yeni bir challenge çekmesine izin verir.

Çözücünün kapsadığı algoritmalar

Aynı metot her iki nesli de karşılar. Legacy şema SHA-1, SHA-256, SHA-384 ve SHA-512 ile desteklenir; proof-of-work v2 ise PBKDF2 ve iteratif SHA ile desteklenir. PBKDF2, ALTCHA’nın kendisinin önerdiği varsayılandır, dolayısıyla bu kapsam yayındaki sitelerin büyük çoğunluğunu içerir.

Argon2id ve scrypt istisnadır ve denenmek yerine reddedilirler: bunlardan birini kullanan bir görev, yaklaşık saniyenin üçte biri içinde ERROR_CAPTCHA_UNSOLVABLE ile geri döner ve asla yeniden denenmez. Bu bilinçli bir tercih. Bellek yoğun bir fonksiyonu yeniden denemek düzeltmez, bu yüzden hemen başarısız olmak meşgul görünmekten iyidir. ALTCHA söz konusu olduğunda bu sonuç, okunamayan bir görseli değil algoritmayı işaret eder ve hata kodu ayrıca şurada ele alınıyor: kendine ait bir rehber.

3. adım: token’ı süresi geçmeden, hiç değiştirmeden geri gönderin

Widget, payload’ını altcha adlı bir form alanında gönderir; dolayısıyla token’ınız da oraya gider. Sessiz sedasız bozulan adım budur.

// Send it exactly as it came back: no trimming,
// no re-encoding, no reordering.
var body = new FormUrlEncodedContent(new Dictionary<string, string>
{
    ["email"] = "[email protected]",
    ["altcha"] = result.Token!,
});

var response = await http.PostAsync("https://example.com/signup", body);

Token, alanları sunucunun kendi HMAC imzasıyla korunan bir JSON belgesinin base64 hâlidir. Her değişiklik onu geçersiz kılar; bu yüzden düzeltme ya da toparlama gibi görünen her şey gönderimi bozar: boşlukları kırpmak, çözüp yeniden kodlamak veya JSON’ı anahtarları farklı sırada olacak şekilde yeniden oluşturmak. Bazı entegrasyonlar payload’ı form alanı yerine bir JSON gövde alanından okur, bu yüzden sayfanın kendi gönderiminin ne yolladığını kontrol edip aynısını yapın.

Bu adımın bozulmasının diğer yolu zamanlamadır. Challenge pencereleri kısadır ve bazı siteler bu pencereleri iki dakika içinde kapatır; süre dolduğunda site, cevabı tam olarak yanlış cevap gibi görünen yalın bir doğrulama hatasıyla reddeder. Hatanın içinde bu ikisinden hangisinin yaşandığını söyleyen hiçbir şey yoktur. Üç alışkanlık bunu önler: challenge’ı uzun bir çalışmanın başında değil, çözümden hemen önce çekin; token’ı onu çözen aynı iş biriminde gönderin; ve bir kişi formu doldururken elinizde token tutmayın.

Burada sizi sınırlayan şey istemcinin kendi yoklama zaman aşımları değil, çünkü ikisi de o iki dakikalık pencereden çok daha uzun. ALTCHA bir tarayıcı oturumu değil CPU işidir; bu yüzden daha uzun olan reCAPTCHA’nınki yerine varsayılan yoklama zaman aşımıyla çalışır.

Yapıcı seçeneğiVarsayılanNeyi kapsar
defaultTimeout120 saniyeALTCHA ve görsel CAPTCHA yoklaması
recaptchaTimeout300 saniyereCAPTCHA, Turnstile ve GeeTest yoklaması
pollingIntervalEn fazla 5 saniyeYoklama 0,25 saniyeden başlar ve kademeli olarak bu değere kadar çıkar

4. adım: çözücünün nerede çalıştığı ve bunun hangi bağlantı modunu gerektirdiği

Yukarıdaki örnekler 127.0.0.1 kullanıyor, çünkü kodunuz ile çözücü aynı makinede olduğunda doğru adres budur. Çözücüyü çağıran kod başka bir yerde çalışmaya başladığı anda, örneğin bir konteynerde, bir derleme ajanında, bir VPS’te veya yönetilen bir sunucuda, loopback artık çözücüyü göstermez ve ilk çözüm bir NetworkException fırlatır.

CapSkip’i Server moduna alın; o zaman bunun yerine ağ adresinizi veya genel IP’nizi dinler, böylece bu ortamların hepsi API üzerinden ona ulaşabilir. İnternet üzerinden gidiyorsanız statik bir genel IP ve yalnızca beklediğiniz adreslere izin veren bir güvenlik duvarı kuralı önerilir. Server modu yalnızca çözücünün nerede dinlediğini değiştirir, başka hiçbir şeyi değiştirmez: donanım yine sizin donanımınızdır ve kullanım yine sınırsızdır. Tek bir derlemenin her iki yerde de çalışması için host değerini bir ortam değişkeninden okuyun. İstemci CAPSKIP_HOST değerini kendi başına okumaz; bu yüzden, aşağıdaki tam örnekte olduğu gibi, onu yapıcıya geçirin.

C# kodunun çalıştığı yerHangi bağlantı modu
CapSkip makinesinde, bir IDE’de veya konsol uygulamasındaLocal modu. 127.0.0.1 gerçekten doğru
Aynı ağdaki başka bir makinedeServer modu, o makinenin özel adresinde
Bir konteyner sunucusunda, VPS’te veya yönetilen bir platformdaStatik bir genel IP ve bir güvenlik duvarı kuralı ile Server modu

Proxy’lerle ilgili ALTCHA’ya özgü bir not. Burada proxy desteklenir, ancak yalnızca challenge çekme işlemi için kullanılır. Yönlendirilecek bir tarayıcı oturumu olmadığından, iş kanıtının kendisine hiçbir etkisi yoktur.

Tam çalışan örnek

// dotnet add package CapSkip
using CapSkip;

var http = new HttpClient();
var solver = new CapSkipClient(
    host: Environment.GetEnvironmentVariable("CAPSKIP_HOST") ?? "127.0.0.1",
    port: 8080);

try
{
    var result = await solver.AltchaAsync(
        "https://example.com/signup",
        new Dictionary<string, object?>
        {
            ["challenge_url"] = "https://example.com/altcha/challenge",
        });

    // Submit here, while the challenge is still fresh.
    var body = new FormUrlEncodedContent(new Dictionary<string, string>
    {
        ["email"] = "[email protected]",
        ["altcha"] = result.Token!,
    });
    var response = await http.PostAsync("https://example.com/signup", body);

    Console.WriteLine($"{(int)response.StatusCode} after counter {result.Number}");
}
catch (ApiException ex)
{
    // ERROR_CAPTCHA_UNSOLVABLE here means Argon2id or scrypt.
    Console.WriteLine($"refused: {ex.Message}");
}
catch (CapSkip.TimeoutException)
{
    Console.WriteLine("gave up waiting; defaultTimeout is 120 seconds");
}

Diğer türler aynı biçimdedir, yalnızca metot değişir. RecaptchaAsync bir sitekey ve bir sayfa URL’si alır, TurnstileAsync ve GeetestAsync aynı şekilde çalışır, görsel çözme ise base64 ile yapılan bir çağrıdır. Metotların tam listesi şurada: C# CAPTCHA çözücü sayfası.

Bir sitekey’den fazlasını gerektiren tek tür, challenge sayfasındaki Turnstile’dır. Ek değerleri şurada ele alınıyor: C# challenge sayfası rehberi.

Sık görülen hatalar ve anlamları

GördüğünüzNedenDüzeltme
Siteden gelen yalın bir doğrulama hatası, token ise sorunsuz görünüyorForm gönderilmeden önce challenge’ın süresi dolduÇekme, çözme ve gönderme işlemlerini tek bir iş biriminde yapın
Yaklaşık saniyenin üçte biri içinde, bir ApiException içinde ERROR_CAPTCHA_UNSOLVABLEChallenge, Argon2id veya scrypt kullanıyorYeniden denenecek bir şey yok. Bu ikisi tasarım gereği reddedilir
Çağrıda bir ValidationExceptionİki challenge seçeneğinden hiçbiri verilmedi ya da ALTCHA’nın kabul etmediği bir seçenek verildiChallenge endpoint’ini ya da challenge belgesini verin ve geri kalan her şeyi çıkarın
İlk çözümde bir NetworkExceptionCapSkip çalışmıyor ya da host ve port yanlışCapSkip’i başlatın, ardından Local modunda mı yoksa Server modunda mı olması gerektiğini kontrol edin
Sonuçtaki Token özelliği null görünüyorToken yalnızca ALTCHA için doldurulurAltchaAsync’i çağırın. Bir ALTCHA sonucunda Code özelliği aynı dizeyi tutar
Derleme, belirsiz bir TimeoutException yüzünden başarısız oluyorO kısa adı hem CapSkip hem System tanımlıyorCapSkip.TimeoutException’ı tam olarak yazın veya CapSkipError’ı yakalayın
Loglarınızda çözüldüğü görünen bir token’ı form reddediyorBir şey payload’ı yeniden kodladı, kırptı veya sırasını değiştirdiDizeyi hiç dokunmadan, olduğu gibi geçirin

FAQ

C# ile ALTCHA çözmek için tarayıcı gerekir mi?

Hayır ve işin işe yarar kısmı da bu. ALTCHA bakılacak bir şey değil, bir hash problemi verir; dolayısıyla iş tamamen CPU üzerinde yapılır ve milisaniyeler içinde biter. WebDriver, headless Chrome veya user agent gerekmez. HttpClient kullanan bir konsol uygulaması yeterlidir; bu da tarayıcı sürmenin zahmetli olacağı bir worker service, bir kuyruk tüketicisi ya da bir derleme adımının içinde sorunsuz çalıştığı anlamına gelir.

Barındırılan bir platformdaki .NET uygulaması çözücüye erişebilir mi?

Evet. Bağlantı ayarlarından CapSkip’i Server moduna alın; böylece loopback yerine bir ağ adresini dinler. Ardından CAPSKIP_HOST’u o adrese yönlendirin. Bir konteyner sunucusu, bir VPS, bir CI ajanı veya yönetilen bir uygulama servisi; hepsi aynı şekilde, aynı HTTP API üzerinden bağlanır. Rota internetten geçiyorsa statik bir genel IP kullanın ve bunu bir güvenlik duvarı kuralıyla sınırlayın. Bu durumların hepsinde çözücü sizin sahip olduğunuz donanımda kalır, dolayısıyla lisans veya çözüm sayısı açısından hiçbir şey değişmez.

Endpoint’i mi challenge belgesini mi vermeliyim?

Belge elinizde yoksa endpoint’i verin. Seçenek sözlüğündeki tek bir girdidir, size bir istek kazandırır ve iş kuyrukta beklerken challenge bayatlarsa çözücü yenisini kendisi çeker. Belgeyi ise şu durumlarda verin: kazıyıcınız belgeyi sayfadan zaten okuduysa, challenge bir endpoint’ten sunulmak yerine HTML içine gömülüyse ya da challenge’ı çekmek sizin kodunuzda olan ama çözücüde olmayan çerezleri veya başlıkları gerektiriyorsa. Bu son durumda proxy seçeneğini bilmeye değer, çünkü ALTCHA’da bu seçenek yalnızca çekme işlemi için geçerlidir.

Sayacım neden her seferinde farklı bir sayı oluyor?

Çünkü o sayı sitenin bir özelliği değil, o belirli challenge’ın cevabıdır. Her challenge kendi salt değerini taşır, bu yüzden onu sağlayan sayı her verilişte değişir ve challenge’ın izin verdiği maxnumber değerine kadar herhangi bir yere denk gelebilir. Büyük bir sayaç yalnızca daha fazla hash gerektiğini gösterir; bu da birkaç milisaniye fazladan süre olarak görünür, başka hiçbir anlamı yoktur. Sayaç, işin gerçekten yapıldığının kanıtı olarak loglarda işe yarar; önbelleğe alınacak bir şey olarak ise işe yaramaz.

Kısa özet

Challenge endpoint’ini widget’tan okuyun, sayfa URL’si ile birlikte tek ALTCHA metoduna verin ve token’ı hiç dokunmadan altcha adlı alana geri gönderin. Çekme, çözme ve gönderme işlemlerini aynı blokta tutun, çünkü challenge penceresi iki dakika içinde kapanabilir ve süresi geçmiş bir challenge tam olarak yanlış cevap gibi görünür. ERROR_CAPTCHA_UNSOLVABLE hatasını yalnızca Argon2id ve scrypt’ten bekleyin; bu ikisi denenmez bile, doğrudan reddedilir. Çağıran kod, çözücüyle aynı makineyi paylaşmayı bıraktığı anda Server moduna geçin.

Yeniden denemeyi nasıl tasarladığınızı değiştiren son bir nokta. Bir yerel captcha çözücü iş kanıtını zaten sahip olduğunuz bir makinede hesapladığı için, süresi geçmiş bir challenge’ı yeniden denemek size kendi CPU’nuzdan yalnızca birkaç milisaniyeye mal olur, başka hiçbir şeye mal olmaz; bu yüzden bayat bir challenge’ı kurtarmaya çalışmak yerine yeni bir challenge çekmeyi rahatça göze alabilirsiniz.