Pull to refresh

NotCaptcha is not a captcha

Spamming and anti-spamming
Недавно в топике habrahabr.ru/blogs/spam/74007 было рассказано о новом виде капчи.

Капча конечно интересная, хоть и спорная, что вызвало много комментариев о том, насколько это удобно для пользователя, и насколько её легко сломать теоретически.

В одном из комментариев было замечено, что по одному и тому же url показываются хоть и разные картинки, но с одинаковым верным ответом. Это навело на мысль о том, что где-то в алгоритме была допущена ошибка.


Скачав установочные файлы капчи я там заметил одну интересную функцию:

function my_decrypt($encrypt_string, $key) {
  $dec = '';
  $arr = explode("\n", wordwrap($encrypt_string, 2, "\n", 1));
  for ($i = 0; $i < count($arr); $i++) {
    $dec .= chr(hexdec($arr[$i])) ^ $key;
  }
  return $dec;
}


* This source code was highlighted with Source Code Highlighter.


Очевидно, что для «шифрования» (если можно так выразиться) используется xor на константу.
Обойти это можно обычным частотным анализом.
Открыв другой файл, мы увидим, что в параметр изображения i записывается список углов (в dec) разделённый символами |, этим мы и воспользуемся.

Т.к. под рукой у меня имелась только MSVS, то код примера будет на C#
static void Main(string[] args)
    {
      while (true) {
        Console.Write("Input image url: ");

        // вытащим аргумент i из запроса
        var str = HttpUtility.ParseQueryString(new UriBuilder(Console.ReadLine()).Query)["i"];
        // распарсим HEX в байты, заодно посчитаем частоты каждого значения
        var bytes = new byte[str.Length / 2];
        var cnt = new Dictionary<byte, int>();
        for (var i = 0; i < bytes.Length; i++) {
          bytes[i] = byte.Parse(str.Substring(i * 2, 2), NumberStyles.HexNumber);
          if (!cnt.ContainsKey(bytes[i]))
            cnt.Add(bytes[i], 0);
          cnt[bytes[i]]++;
        }
        // найдём среднее, учитывая что в основном строка содержит цифры, то это будет около кода символа 5
        var avg = cnt.Keys.Average(p => (decimal)p);
        // прикинем, сколько картинок содержит эта строка
        var nums = (bytes.Length + 1) / 3.5;

        // выберем все коды, которые могут быть разделителем |
        var candidates = cnt.Where(kvp => Math.Abs(kvp.Value - nums) <= 2).ToList().ConvertAll(p => p.Key);
        // выберем из них тот, который максимально удалён от чисел
        candidates.Sort((a, b) => (int)(Math.Abs(b - avg) - Math.Abs(a - avg)));
        // посчитаем «ключ» использованный для xor
        var key = (byte)(candidates[0] ^ Encoding.ASCII.GetBytes("|")[0]);
        // по-xor'им обратно
        for (var i = 0; i < bytes.Length; i++)
          bytes[i] ^= key;

        // и переведём в углы
        var angles = Encoding.ASCII.GetString(bytes).Split('|').ToList().ConvertAll(p => int.Parse(p));

        Console.WriteLine("Image at position {0} is upright", angles.IndexOf(0));
      }
    }


* This source code was highlighted with Source Code Highlighter.


Пользоваться так: скомпилировать, запустить, и на вход подавать полный адрес картинки типа http://notcaptcha.webjema.com/wp-content/plugins/wp-notcaptcha/lib/notcaptcha.php?i=424b430f4141460f4144430f4043430f430f45430f4a430f424046&r=1257161610

В принципе, если бы не этот «детский» алгоритм шифрования, то капчу было бы не так уж и легко поломать, но это уже совсем другая история…

P.S.: на закуску:
image
Tags:
Hubs:
Total votes 81: ↑71 and ↓10 +61
Views 2.8K
Comments Comments 21