Недавно VPNBook стал публиковать пароль вместо прямого текста в виде изображения. «Ну как же так» — подумал я и начал искать пути решения этой проблемы. Распознаем «картиночный» пароль VPNBook на PHP. И, конечно, скрипт для Mikrotik.
Уже давно я у себя на роутере (Mikrotik) настроил автоматический бесплатный PPTP VPN туннель от VPNBook.com и успешно до недавнего времени им пользовался. Не буду вдаваться в подробности, они описаны в статье "Настраиваем автоматическое получение пароля для VPN на Mikrotik". До проблемы пароль для VPNBook можно было просто извлечь из html страницы, например, так:
А с недавнего времени пароль стал «картинкой». И первая мысль была воспользоваться оптическим распознаванием текста. Я стал пробовать онлайн и оффлайн сервисы OCR, которыми можно было бы распознать пароль. Передаю привет хабражителю Winand, с которым у нас на эту тему состоялась переписка. В общем последний OCR, с которым я возился, был Tesseract, который «из коробки» определял пароль, но с ошибками. Но его можно обучить новым шрифтам, чем я и собирался заняться. Когда я подбирал шрифт, похожий на «картиночный», возникла мысль, что это что-то простое, хотя и похоже на teminal из Windows или terminus font из Linux. И voila — это оказался просто встроенный шрифт PHP под номером (размер) 5. Далее я забросил OCR и написал скрипт на PHP, который ищет символы «картиночного» пароля по сгенерированному словарю. Словарь — это набор изображений возможных символов пароля того же цвета и размера. Поиск делается по совпадению изображений. Вот такой нехитрый реверс-инжиниринг. Предполагаю, что текущий вариант изображения у VPNBook продержится недолго, учитывая его примитивность.
Сприпт возвращает строку-пароль.
С подсказки vvsvic привожу простую реализацию альтернативного скрипта, для извлечения пароля из твиттера VPNBook (https://twitter.com/vpnbook/)
Скрипт нужно вызывать каждую минуту из шедулера. Скрипт мониторит статус PPTP соединения и при разрыве связи вызывает всю процедуру запроса нового пароля, таким образом микротик не«флудит попытками открытия соединение в течении нескольких часов с неправильным паролем, а переподключение делается за 1 минуту. Также тут отслеживаются ошибки on-error для fetch и file get, чтобы точнее определить, что пароль получен.
Еще рекомендую добавить отключение PPTP интерфейса по разрыву связи (событие on-down) в PPP-профиле, чтобы переподключение вообще не флудило даже в течении 1 минуты.
Соответственно, основной скрипт в течении 1 минуты в случае удачного получения нового пароля поднимет pptp-out1 соединение.
Уже давно я у себя на роутере (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