Как я перехватывал трафик покер рума или «Пишем свой MitM SSL прокси на C#»

Однажды у меня появилась навязчивая идея: посмотреть, а что же там такого покерный клиент отправляет на сервер. Как Вы понимаете, крупные покерные румы используют SSL для передачи данных. Протоколы, основанные на асимметричном шифровании, подвержены только одному известному мне виду атак — MitM (Man in the middle — человек посередине).

Помаявшись с тонной софта, предназначенного для реализации 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 соединения клиента и сервера отправляем друг другу.
Процес рукопожатия C#
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 написан.

Осталось добавить в него немного блекджека. Например, для разбора идущего через него трафика, выдирания карт пользователя и отправки нам и т. д…
Я написал разборщик трафика на лету, что бы можно было удобно мониторить, что отправляет клиент, и соотносить это с моими действиями. Демонстрация того, что получилось у меня:


Исходники 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();
            }
        }
    }
}



Комментарии 33

    +2
    Прикуп-то удалось узнать?
      +5
      Нет :), во первых я расшифровывал трафик клиента, во вторых есть точная инфа что сервер эти данные не отправляет.
      Ну и в заключение, цель преследовал несколько другую, разобраться что отправляет клиент обо мне.
      Например он шлёт серийный номер жесткого, инфу о том что запущен wmware и т.д.
        +5
        Fiddler ж есть — умеет «из коробки» снифить SSL\TLS подключения… Если только для целей изучения, то подойдет.
        UPD: ниже ответили.
          0
          Правда? Если это так, то очень обидно, можно показать ссылку на то где описано как снифать именно SSL(TCP), а так же где я могу сконфигурировать какой сертификат он передаёт клиенту.
          UPD: угу(
            0
            Всё умеет. Только по дефолту выключена расшифровка трафика. Ну и написан тоже на .Net))
      +1
      А Fiddler для этого не подошел бы?
        +2
        HTTP/HTTPS… не TCP+SSL
          0
          Из существующего софта, больше всего подходит Mallory
            0
            Не заметил что речь не о HTTP.
              +1
              Ох, если бы покерные клиенты работали по http и были написаны на js(речь о крупных покер румах), ох, тогда бы мы разгулялись :)
          +2
          ай-ай-ай разработчикам. хеш корневого сертификата неплохо бы сверить при подключении
            +1
            Да вообще не проще ли было зашить в покерный клиент публичный ключ сервера?
              0
              проще, но отозвать нельзя
                +1
                Авто обновление же )))
                +3
                Толку мало, вшитый ключ можно и достать и пропатчить. Так некоторые продукты делают, но это никого не останавливает.
                0
                С другой стороны, кто мешает как угодно пропатчить это самое место в коде?
                  +2
                  все же сложнее, а прокси почти любой может сделать.
                0
                Я делал чуть по-другому.
                SSL-соединение устанавливается только между прокси и сервером.
                Клиента патчим (в моем случае это было несложно) чтобы он открывал незащищенное соединение на прокси.
                В результате нам вообще не приходится возиться с самоподписанными сертификатами.

                  0
                  Да, подмена https на http возможна :), но эта история не о том.
                    0
                    А легко ли найти место где патчить?
                      0
                      Я имел в виду разновидность атаки на https, когда клиенту отправляют редирект на якобы http версию, и от клиента получают расшифрованный трафик, что позволяет, избежать проблем с сертификатами.

                      Касательно того, легко ли найти место где патчить, то думаю это просто не реально сложно, а с покерными румами ещё и не возможно, там же каких только проверок, целостности самого процесса и исполняемого файла нет. То есть, если мы найдём где пропатчить, то не факт что клиент не поймёт что пропатчен и не пошлёт нас подальше, ещё и репорт накатает хД,

                      Впрочем я могу ошибаться, и возможно это значительно проще чем я себе представляю.

                  –5
                  Добрый день. Напишите письмо в техническую поддержку покер-рума и попросите вознаграждение за найденную ошибку. Сфера азартных игр достаточно прибыльное дело, и если использовать эту уязвимость нельзя, то даже для имиджа это может стать хорошим ударом.

                  Если Вам необходима помощь в данном деле, то стоит написать в русскую поддержку покестратедж, они найдут способ поспособствовать вашей награде.
                    0
                    Да, я подумывал, написать в техподдержку, и рассказать о том, что сделал(похвастаться, сделать хорошее дело, и получить шанс на вознаграждение), всё время меня останавливало одно: я ещё не до конца разобрался в этом. Когда ты находишь какую то уязвимость, ты всегда пытаешься раскрутить её по максимуму, найти ту грань, до который ты сможешь зайти, будь то угнать куки админа через xss или же залить шел на сервер.

                    Скорее всего, я воспользуюсь вашим советом. Но конечно же не по поводу уязвимости ssl, а по тому, что смог расшифровать передаваемый через ssl трафик. В связи с этой новостью, это вполне серьёзно. :)
                      +4
                      Нет тут уязвимости — у вас есть приватный ключ, значит вы в любом случае можете читать сосбтвенный траффик.
                      0
                      Мне как инструмент для подобного рода анализа вспомнилась Frida (http://www.frida.re/docs/quickstart/). Она позволяет перехватывать трафик еще до того как он уйдет в функцию отправки данных или в функцию кодирования данных. Т.е. даже делать свой прокси сервер не пришлось бы.
                        0
                        Выглядит круто, не знал про такую разновидность атак. К сожаление, не очень представляю её принцип действия, насколько лояльно отнёсся бы покерный клиент к такому вторжению, а так же, насколько это осуществимо под виндой. Помимо прочего, очень сложно отловить то что нужно. Это всё к тому, что навряд ли применимо в моём случае.

                        А так, да, реально очень круто, надо будет проресёчить возможности подобных софтин.
                        +4
                        Перехват трафика это хорошо только вот шансов что это будет прибыльно — никаких. По крайней мере если вы реверсите протоколы биржи вы потом можете прописать действия в железе и торговать быстрее конкуретнов. Я не думаю что покер рум даст вам инфу по картам других игроков. Впрочем, если бы это было так, автор бы об этом не написал.

                        Единственная польза для этого — это вытаскивать данные для автоматического анализа. Сам такое делаю, но не на покере — покерное поле хорошо распахано, там 50% ботов и ловить что-то стоящее очень сложно. Ловить можно в других играх, например тех где обсчет модели для большинства пользователей недоступен ввиду отсутствия вычислительных мощностей.
                          0
                          Кстати, что насчет онлайн игр? Там же тоже реальыне деньги крутятся. WoW и прочее.
                            0
                            Карты проще. На покер пошли все потому что там все играют. Покеррумы начали обороняться, но против умного разраба не попрешь, так что там засилье ботов. ОП написал как трафик перехватывать, это значит что можно вируалить, например, и на той стороне знать не будут. Но этого увы мало, т.к. покер игра неоднозначная и стат арб там очень болезненный.

                            Насчет WoW не знаю, меня интересуют игры где люди играют на деньги думая что сложность игры не обсчитать на компьютере.
                              0
                              Можно, пожалуйста, пример такой игры?
                                0
                                Не-а :)
                          +1
                          Если серверная сторона не видит разницы между HTTPS и HTTP соединениями, берем отладчик, реверсим исполняемый файл, и меняем https на http, ну и порт, если отличен от 80.
                          Проверено десятком разного рода софта.
                            0
                            Ну теперь можно писать бота который будет за Вас круглосуточно играть)

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

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