Помаявшись с тонной софта, предназначенного для реализации MitM на SSL соединение, я пришел к выводу, что руки растут не из того места либо у разработчиков данных инструментов, либо у меня. Но идея была жутко навязчивая, и было принято решение сделать всё вручную. Если интересно, что же из всего этого вышло, прошу под кат.
Данная статья просвещена написанию простого инструмента для реализации MitM-атаки. Те, кто не знакомы с тем, что такое MitM, могут почитать об этом тут.
Цель
В качестве подопытного был выбран клиент Cake Poker по причине моего длительного знакомства с ним. Началось все с того, что я просто запустил покерный клиент и полез в старый добрый менеджер ресурсов, в котором нашел с десяток подключений от него.
Поэкспериментировав, я смог выделить соединение, которое держится всегда. Его я и взял на прицел для проведение MitM атаки.
Источник соединения известен, конечная цель известна — lb6.playdata.co.uk.
Перенаправление трафика
Теперь необходимо было вклиниться между клиентом и сервером. Нечего особо хитрого изобретать не стал, ибо незачем — просто добавил в host доменные имена, соотнесённые с 127.0.0.1. Не сложно догадаться, что если есть lb6.playdata.co.uk, то есть и lb1.playdata.co.uk, и lb8.playdata.co.uk. С ними поступил аналогично, занеся в host, т.к. конечные инстансы, как я понял, выбираются по расположению звёзд. При запуске покерного клиента он повисает в ожидании подключения. Замечательно. Это означает что трафик был перенаправлен на нашу машинку. Идём дальше.
Прокси
Следующей задачей было написание прокси на C#. Да-да, простой прокси, для заготовки будущей программы. Чтобы не заниматься изобретением велосипеда, я по-быстрому нагуглил подходящее для меня решение: TCP Proxy in C# using Task Parallel Library.
Немного его отрефакторил (ненавижу, когда много вложенности), захардкодил конечную точку подключения и запустил. Запускаю покерного клиента — всё работает. В диспетчере ресурсов можем видеть, что трафик от покерного клиента идёт к моему прокси, а от него на сервер и обратно.
MitM на SSL
Далее нам предстоит реализовать MitM-атаку на SSL. Разделим её на два этапа: первый — соединение клиента с прокси; второй — прокси с сервером.
Для реализации первого этапа, когда клиент подключится к прокси, мы не будем отправлять пришедшие данные дальше, а начнём, так называемую, процедуру рукопожатия. На C# это можно сделать с помощью экземпляра класса SslStream, построенного поверх уже созданного NetworkStream. В момент создания передаётся информация о протоколе и прочая специфическая информация.
После этого, передаём клиенту свой сертификат. Это делается с помощью метода AuthenticateAsServer класса SslStream, куда мы должны передать путь до файла сертификата.
Файл x509 сертификата был сгенерирована с помощью утилиты Makecert, которую можно вызвать из девелоперской консоли Visual Studio. Пришлось немного помучиться с параметрами, но всё получилось. Вот неплохое описание того, как ей можно пользоваться: SSL communication in C#. В качестве имени укажем *.playdata.co.uk. Это имя покрывает все домены, которые используются покерным клиентом.
makecert -n CN=MyCA -cy authority -a sha1 -sv “MyCA.pvk” -r “MyCA.cer” //Создаём сертификат ЦА certmgr -add -all -c “MyCA.cer” -s -r LocalMachine Root //Добавляем в довереные коневые центры сертификаты makecert -n CN=*.playdata.co.uk -ic MyCA.cer -iv MyCA.pvk -a sha1 -sky exchange -pe -sr currentuser -ss my SslServer.cer //Создаём серверный сертификат
Мы сгенерировали серверный закрытый ключ и ключ УЦ (удостоверительного центра), которым подписан наш ключ. Ключ УЦ помещаем в «Доверенные корневые центры сертификации», и вуаля! Если посмотреть на наш серверный ключик средствами просмотра ключей Windows, то мы увидим, что система считает его валидным, как и покерный клиент, который запущен в нашей системе.
Путь до полученого сертификата мы передаём в метод AuthenticateAsServer. Если всё прошло хорошо, то у нас получится SSL соединение от клиента до прокси, в которое клиент будет посылать данные. Теперь необходимо давать клиенту адекватные ответы на его запросы. Для этого нам потребуется реализовать второй этап MitM-атаки, а именно построить SSL соединение от прокси до сервера. Так же строим SslStream поверх NetworkStream до сервера и авторизовываемся с помощью метода AuthenticateAsClient. Данные, приходящие из SSL соединения клиента и сервера отправляем друг другу.
var certificate = new X509Certificate("SslServer.cer", "123"); var clientStream = new SslStream(client.GetStream(), false); clientStream.AuthenticateAsServer(certificate, false, System.Security.Authentication.SslProtocols.Default, false); var server = new TcpClient("200.26.205.63", 4520); var serverSslStream = new SslStream(server.GetStream(), false, SslValidationCallback, null); serverSslStream.AuthenticateAsClient("lb3.playdata.co.uk");
Через некоторое время работы можно будет заметить в менеджере ресурсов расхождение количества отправленных байт на прокси и от него. Это обусловлено тем, что при шифровании разные ключи дают разный по размеру результат.
Заключение
Что же дальше? А дальше мы добавим код для сохранения данных в текстовый файл, чтобы их можно было проанализировать то, что передаёт покерный клиент серверу. Собственно, всё, MitM Proxy написан.
Осталось добавить в него немного блекджека. Например, для разбора идущего через него трафика, выдирания карт пользователя и отправки нам и т. д…
Я написал разборщик трафика на лету, что бы можно было удобно мониторить, что отправляет клиент, и соотносить это с моими действиями. Демонстрация того, что получилось у меня:
using System; using System.Diagnostics; using System.IO; using System.Net; using System.Net.Security; using System.Net.Sockets; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using ConnectionAnalizer; namespace MITMProxy { class Program { static readonly TcpListener Listener = new TcpListener(IPAddress.Any, 4520); const int BufferSize = 4096; static void Main() { Listener.Start(); new Task(() => { while (true) { var client = Listener.AcceptTcpClient(); new Task(() => AcceptConnection(client)).Start(); } }).Start(); Debug.WriteLine("Server listening on port 4502. Press enter to exit."); Console.ReadLine(); Listener.Stop(); } private static void AcceptConnection(TcpClient client) { try { var certificate = new X509Certificate("SslServer.cer", "123"); var clientStream = new SslStream(client.GetStream(), false); clientStream.AuthenticateAsServer(certificate, false, System.Security.Authentication.SslProtocols.Default, false); var server = new TcpClient("200.26.205.63", 4520); var serverSslStream = new SslStream(server.GetStream(), false, SslValidationCallback, null); serverSslStream.AuthenticateAsClient("lb3.playdata.co.uk"); new Task(() => ReadFromClient(client, clientStream, serverSslStream)).Start(); new Task(() => ReadFromServer(serverSslStream, clientStream)).Start(); } catch (Exception ex) { Console.WriteLine(ex.Message); throw; } } private static bool SslValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslpolicyerrors) { return true; } private static void ReadFromServer(Stream serverStream, Stream clientStream) { var message = new byte[BufferSize]; while (true) { int serverBytes; try { serverBytes = serverStream.Read(message, 0, BufferSize); clientStream.Write(message, 0, serverBytes); } catch { break; } if (serverBytes == 0) { break; } } } private static void ReadFromClient(TcpClient client, Stream clientStream, Stream serverStream) { var message = new byte[BufferSize]; var fileInfo = new FileInfo("client"); if (!fileInfo.Exists) fileInfo.Create().Dispose(); using (var stream = fileInfo.OpenWrite()) { while (true) { int clientBytes; try { clientBytes = clientStream.Read(message, 0, BufferSize); } catch { break; } if (clientBytes == 0) { break; } serverStream.Write(message, 0, clientBytes); memoryStream.Write(message, 0, clientBytes); stream.Write(message, 0, clientBytes); } client.Close(); } } } }
ссылка на оригинал статьи http://habrahabr.ru/post/213397/
Добавить комментарий