Почти OCR для получения пароля VPNBook. PHP + Mikrotik

    Недавно VPNBook стал публиковать пароль вместо прямого текста в виде изображения. «Ну как же так» — подумал я и начал искать пути решения этой проблемы. Распознаем «картиночный» пароль VPNBook на PHP. И, конечно, скрипт для Mikrotik.

    Уже давно я у себя на роутере (Mikrotik) настроил автоматический бесплатный PPTP VPN туннель от VPNBook.com и успешно до недавнего времени им пользовался. Не буду вдаваться в подробности, они описаны в статье "Настраиваем автоматическое получение пароля для VPN на Mikrotik". До проблемы пароль для VPNBook можно было просто извлечь из html страницы, например, так:

    preg_match('/Password: <strong>([^<]+)/', $homepage, $matches);
    print($matches[1])

    А с недавнего времени пароль стал «картинкой». И первая мысль была воспользоваться оптическим распознаванием текста. Я стал пробовать онлайн и оффлайн сервисы OCR, которыми можно было бы распознать пароль. Передаю привет хабражителю Winand, с которым у нас на эту тему состоялась переписка. В общем последний OCR, с которым я возился, был Tesseract, который «из коробки» определял пароль, но с ошибками. Но его можно обучить новым шрифтам, чем я и собирался заняться. Когда я подбирал шрифт, похожий на «картиночный», возникла мысль, что это что-то простое, хотя и похоже на teminal из Windows или terminus font из Linux. И voila — это оказался просто встроенный шрифт PHP под номером (размер) 5. Далее я забросил OCR и написал скрипт на PHP, который ищет символы «картиночного» пароля по сгенерированному словарю. Словарь — это набор изображений возможных символов пароля того же цвета и размера. Поиск делается по совпадению изображений. Вот такой нехитрый реверс-инжиниринг. Предполагаю, что текущий вариант изображения у VPNBook продержится недолго, учитывая его примитивность.

    Скрипт vpnbook.php


    Сприпт возвращает строку-пароль.

    <?php
    // размер символа
    $wchar = 9;
    $hchar = 13;
    $strDict = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 ';
    $imgDict = imagecreatetruecolor(2 + strlen($strDict)* $wchar, $hchar);
    $bg = imagecolorallocate($imgDict, 0xF6, 0xF6, 0xF6);
    $textcolor = imagecolorallocate($imgDict, 0x4C, 0x4C, 0x4C);
    imagefill($imgDict, 0, 0, $bg);
    imagestring($imgDict, 5, 2, 0, $strDict, $textcolor);
    
    
    // инициализируем cURL
    $ch = curl_init();
    // устанавливаем url, с которого будем получать данные
    curl_setopt($ch, CURLOPT_URL, 'https://www.vpnbook.com/password.php');
    // устанавливаем опцию, чтобы содержимое вернулось нам в string
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_BINARYTRANSFER, 1); // also, this seems wise considering output is image.
    // выполняем запрос
    $output = curl_exec($ch);
    // закрываем cURL
    curl_close($ch);
    
    $imgOCR = imagecreatefromstring($output);
    // $imgOCR = imageCreateFromPng('password.png');
    // в текущее изображение может поместиться 10 полных символов. 2 + 10*9 = 92 < 100
    $maxchar = floor((imagesx($imgOCR) - 2) / 9);
    $imgBox = imagecreatetruecolor($wchar, $hchar);
    $hashDict = Array();
    
    // генерируем словарь
    for ($k = 0; $k < strlen($strDict) ; $k++) {
    	imagecopy($imgBox, $imgDict, 0, 0, 2 + $k * $wchar, 0, $wchar, $hchar);
    	$hashStr = "";
    	for($y = 0; $y < $hchar ; $y++)
    		for($x = 0; $x < $wchar; $x++) $hashStr .= (imagecolorat($imgBox, $x, $y) != 0xF6F6F6)? '1': '0';
    	$hashDict[$hashStr] = $strDict[$k];
    }
    
    // ищем символы по словарю
    for ($k = 0; $k < $maxchar ; $k++) {
    	imagecopy($imgBox, $imgOCR, 0, 0, 2 + $k * $wchar, 0, $wchar, $hchar);
    	$hashStr = "";
    	for($y = 0; $y < $hchar ; $y++)
    		for($x = 0; $x < $wchar; $x++) $hashStr .= (imagecolorat($imgBox, $x, $y) != 0xF6F6F6)? '1': '0';
    	$tempchar = $hashDict[$hashStr];
    	if ($tempchar==' ') break;
    	print($tempchar);
    }
    
    /*header('Content-type: image/png');
    imagepng($imgOCR);
    */
    //var_dump($hashDict);
    imagedestroy($imgDict);
    imagedestroy($imgOCR);
    imagedestroy($imgBox);
    ?>


    План Б. Пароль из twitter


    С подсказки vvsvic привожу простую реализацию альтернативного скрипта, для извлечения пароля из твиттера VPNBook (https://twitter.com/vpnbook/)
    <?php
    function url_get_html($url) {
    	// инициализируем cURL
    	$ch = curl_init();
    	// устанавливаем url с которого будем получать данные
    	curl_setopt($ch, CURLOPT_URL, $url);
    	// устанавливаем опцию чтобы содержимое вернулось нам в string
    	curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    	// выполняем запрос
    	$output = curl_exec($ch);
    	// закрываем cURL
    	curl_close($ch);
    	// возвращаем содержимое
    	return $output;
    }
    
    $homepage = url_get_html('https://twitter.com/vpnbook');
    preg_match('/Password: ([^<]+)/', $homepage, $matches);
    
    // Print the entire match result
    // var_dump($matches);
    print($matches[1])
    // print_r($matches);
    ?>
    


    Скрипт Mikrotik VPNBook


    Скрипт нужно вызывать каждую минуту из шедулера. Скрипт мониторит статус PPTP соединения и при разрыве связи вызывает всю процедуру запроса нового пароля, таким образом микротик не«флудит попытками открытия соединение в течении нескольких часов с неправильным паролем, а переподключение делается за 1 минуту. Также тут отслеживаются ошибки on-error для fetch и file get, чтобы точнее определить, что пароль получен.

    # VPNBookScript v2
    :global VPNBookpIfName "pptp-out1"
    :global VPNBookServerAddresses {"euro217.vpnbook.com";"euro214.vpnbook.com";"us1.vpnbook.com";"us2.vpnbook.com";"ca1.vpnbook.com";"de233.vpnbook.com";"fr1.vpnbook.com"}
    #:if ([:typeof $VPNBookServerAddresses] != "array") do={
    #  :set VPNBookServerAddresses {"euro217.vpnbook.com";"euro214.vpnbook.com";"us1.vpnbook.com";"us2.vpnbook.com";"ca1.vpnbook.com";"de233.vpnbook.com"}
    #}
    
    :global VPNBookErr false
    :global VPNBookPassFile "VPNBookPass.txt"
    :global VPNBookPass
    :global VPNBookRun
    #:global TToken "4.....................2"
    #:global TChatId "2342432...9"
    
    
    :global VPNBookServerIndex
    :if ([:typeof $VPNBookServerIndex] != "num") do={:set VPNBookServerIndex 0}
    
    :if ([/interface pptp-client get $VPNBookpIfName running]) do={
      :set VPNBookRun true
    } else {
      :if (!$VPNBookRun) do={
        :set VPNBookServerIndex ($VPNBookServerIndex + 1)
        :if ($VPNBookServerIndex>=[:len $VPNBookServerAddresses]) do={:set VPNBookServerIndex 0}
      } else {
        :set VPNBookRun false
      }
      :if (![/interface pptp-client get $VPNBookpIfName disabled]) do={/interface pptp-client set $VPNBookpIfName disabled=yes}
      :do {/tool fetch url="http://server/vpnbookpass.php" dst-path=$VPNBookPassFile} on-error={:set VPNBookErr true}
      :delay 2
      :do {:set VPNBookPass [/file get $VPNBookPassFile contents]} on-error={:set VPNBookErr true}
      :if (!$VPNBookErr) do={
        :if ([/interface pptp-client get $VPNBookpIfName password] != $VPNBookPass) do={/interface pptp-client set $VPNBookpIfName password=$VPNBookPass}
        :if ([/interface pptp-client get $VPNBookpIfName connect-to] != $VPNBookServerAddresses->$VPNBookServerIndex) do={/interface pptp-client set $VPNBookpIfName connect-to=($VPNBookServerAddresses->$VPNBookServerIndex)}
        :log info ("VPNBook: Attempt to connect to: ".($VPNBookServerAddresses->$VPNBookServerIndex).". Password: $VPNBookPass")
    #    /tool fetch url=("https://api.telegram.org/bot$TToken/sendmessage\?chat_id=$TChatId&text=VPNBook: Attempt to connect to: ".($VPNBookServerAddresses->$VPNBookServerIndex).". Password: $VPNBookPass") keep-result=no
        /interface pptp-client set $VPNBookpIfName disabled=no
      }
    }

    Еще рекомендую добавить отключение PPTP интерфейса по разрыву связи (событие on-down) в PPP-профиле, чтобы переподключение вообще не флудило даже в течении 1 минуты.

    Соответственно, основной скрипт в течении 1 минуты в случае удачного получения нового пароля поднимет pptp-out1 соединение.

    add change-tcp-mss=yes name=VPNBook on-down=\
        ":if (![/interface pptp-client get pptp-out1 disabled]) do={\r\
        \n  /interface pptp-client set pptp-out1 disabled=yes\r\
        \n}" only-one=yes use-compression=yes use-encryption=required use-ipv6=no use-mpls=no use-upnp=no
    • +14
    • 3,7k
    • 9
    Поделиться публикацией
    Комментарии 9
      0
      интересно, но в Китае оно не работает
        0
        Дай тебе Бог здоровья, добрый человек :)
          –3
          Я так в первом классе писал
            0
            А зачем такие сложности?
            Они же в Твиттере есть. Там все по старому осталось.
              0
              Эту возможность я как-то упустил. Но, вопрос: оперативно ли появляется твит с новый паролем? В любом случае я добавлю в заметку план Б, спасибо.
                0
                В этот раз Твиттер не обновился.
                0
                Спасибо, не знал что у них твиттер есть. Тоже уже хотел научить свой роутер распознавать картинки. :)
                0
                Может кто посоветует аналогичные сервисы? Имею ввиду VPNBook
                  0
                  Автор капчи (VPNBook) не учел, что «u» и «y» у него сольются в идентичный символ. Пользователи просто не смогут распознать «y» в обрезанном символе «u».
                  Костыль для обхода проблемы, вставить в блок "// ищем символы по словарю":
                    $tempchar = $hashDict[$hashStr];
                    if ($tempchar=='u' || $tempchar=='y') // проблема совпадения символов
                      $tempchar = (mt_rand(0, 1))? 'u': 'y';
                      //$tempchar = (time() / 60 % 60 % 2)? 'u': 'y';
                    elseif ($tempchar==' ') break;

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое