Сегодня мы будем учиться писать троянчик. Небольшой, но СВОЙ! Почему? Конечно-конечно, троянов в сети навалом, но рано или поздно они =вдруг= начинают определяться антивирусами, приходиться ждать апдейта етц. Ожидание — самый скучный повод.
Не знаю как вы, но я ждать не люблю, да и использовать чужие трояны это совсем не по->
После точки, как учили в школе, пишем с большой буквы. Так вот, о чем я? Писать я буду, как обычно, на своем любимом Builder C++. Если у вас руки растут не оттуда, откуда у меня ноги, то без проблем все переделаете под Дельфи, если припрет конечно.
Естественно, наш троян будет состоять из 2х частей =) Серверной (отправляемой как подарок ламерюге) и клиентской (оставим себе на память). Надо сказать, что я давно не юзал никакие трояны, так что не знаю что там сейчас они умеют делать, может уже и коврик под мышкой научились двигать или корпус компа открывать. Мы же пишем «троянчик», поэтому делать он будет только следующее:
1) читать, удалять, запускать файлы на удаленном компьютере
Удаленный рабочий стол средствами Python
2) работать с реестром на удаленном компьютере
3) ну и традиционный набор разных бесполезных функций, типа открытие CD-ROM’a, смена клавишей мышки etc.
Перекурили и поехали!
Использовать будем стандартные компоненты TClientSocket и TServerSocket.
Начнем с клиента. Набрасываем простенький интерфейс и приступаем к реализации. Управлять удаленным компьютером будем с помощью специальных команд. Для примера пускай структура их будет такая:
Nпараметры. N — цифра. Каждому действию присвоен свой код. Т.е. например 1 — перезагрузка, 2 — чтение файла и т.д. Главное чтоб было однозначное соответствие между тем что хотим сделать (команда передаваемая клиентом) и тем что выполняет программа (обработка команды на сервере). С этим разобрались. Теперь параметры.
Бывает мало передать только номер команды. Конечно, чтобы перезагрузить компьютер никаких параметров не надо (хотя можно и здесь передать параметры в функцию перезагрузки), но как например реализовать удаление файла не передавая параметра? Мы передаем команду на удаление файла, но какого?! Для этого будем использовать параметры.
В качестве параметра в данном случае будет передаваться имя файла. Бывает, что мало передать один параметр. Например надо прочитать n-строчек из file.txt. Здесь необходимо передать 2 параметра. В нашем примере параметры отделяются друг от друга комбинацией »
» — перевод каретки. Объясняется данная структура тем, что в сервере мы сначала помещаем полученную команду в TStringList и потом можем спокойно обращаться к любой строчке этого TStringList’а через свойство Strings[i], где i-номер строчки, а соответственно и номер параметра. В общем, это вещи достаточно очевидные.
Вот так, вроде бы только начав писать клиентскую часть мы ее уже почти и закончили! Ведь на самом деле ничего кроме отсылки команд и приема ответов от сервера она делать и не должна. Для приема ответов просто создадим поле TMemo и добавим обработчик события OnRead нашего компонента TClientSocket:
Как пользоваться NjRAT? Приколы вируса удаленного доступа
Вот и все. Клиент законен! Переходим к серверу.
Сервер будет чуть пообъемнее. Вначале определимся с задачами:
1) получение команд
2) их обработка и выполнение соответствующих действий
3) отсылка ответа клиенту (должны же мы знать что происходит на сервере)
Реализуем.
Во-первых, никакого визуального оформления естественно не будет =) Поэтому на форму поместим только 1 компонент: TServerSocket . Инициализацию его проведем в функции FormCreate(). Хотя можно было бы просто прописать 2 параметра в Object Inspector’е. Но раз уж сделали, так сделали =)
Итак, указали порт, активизировали сокет. Теперь обрабатываем событие ClientRead , т.е. получение данных сокетом. Комментирую на примере:
Теперь немного подробнее. Мы пишем специальный класс TTrojanUtilites , в котором реализуем все необходимые функции. В RecognizeCommand (String Directive) мы только отделяем команду от параметров и запускаем необходимые методы TTrojanUtilites, передавая по необходимости в них параметры.
Реализация TTrojanUtilites есть то, чем мы сейчас займемся. Класс оформим в отдельном модуле, не забудьте подключить его.
Поехали. Во-первых, подключаем #include — необходимо для реализации работы с CD-ROM’ом. Далее пишем все необходимые методы.
Краткие комментарии на примере:
Вот тут я не отвечаю за все ОСи, перезагрузка-то будет, но хотелось бы сделать ее как после нажатия кнопки RESET, а так будет послано сообщение WM_. ENDSESSION etc. Короче, пробуйте сами. Могу только подкинуть основные направления поиска: смотри функции ExitWindows(), ExitWindowsEx(), InitiateSystemShutdown() и AbortSystemShutdown(). Для особо продвинутых могу предложить вариант написать надежный ребут под определенную ось и чипсет =) Например, мой прошлый комп стабильно резетился (только в Win9x, в NT не работало) следующей вставочкой:
Откуда цифры? Читайте доки по чипсету =) Одним словом, универсального метода перезагрузить комп без WM_. ENDSESSION я не знаю. Если вы знаете — напишите пару строк, не в падлу =)
Запись в реестр аналогична, только используется метод WriteBool(TTSL->Strings[2], false ); для записи булевского значения, WriteInteger(TTSL->Strings[2], StrToInt(TTSL->Strings[4])); — для числового, WriteString(TTSL->Strings[2], TTSL->Strings[4]); — для строкового.
Для запуска файлов на удаленном компьютере используем следующую функцию:
Файл запускается в той программе, с которой ассоциирован запуск данного типа файлов, т.е. вы можете запускать не толкьо exe, com, bat, но и любые другие файлы. Html-файл откроется в Explorer’e, Opere, Netscape или еще в чем-то. Хрен знает чем там ламерюга пользуется. Можете запустить ему мп3-шку Децла послушать или Бритни какой-нить, пусть проблюется =)
Идем дальше. Парочка бесполезных функций:
Можно сделать переключение кнопок обратно (передайте в SwapMouseButton параметр false), но я бы посоветовал поставить таймер и каждую секунду переключать кнопки туда-сюда, веселее будет =) В падлу сейчас уже добавлять, но вкратце это будет так: добавляем TTimer на форму, устанавливаем необходимый интервал, и в обработчике события OnTimer прописываем вызов функции SwapMouseButton с параметром, противоположным прошлому вызову. Млин, все-таки все расписал =)
Теперь довольно важная функция:
Данная функция НЕ удаляет файл сразу. Это сделано специально, так как файлы могут использоваться системой и их удаление возможно только после перезагрузки, данная функция помечает файл к удалению, а удалиться он только после перезагрузки, которую вы легко можете вызвать.
Да, еще маленькое примечание: данная функция удаляет только 1 файл, если вы пометите второй файл к удалению — то будет удален только он. А первый останется нетронутым. Немного измените функцию — и можно будет удалять файлы пачками =) Могу еще добавить немного инфы на случай, если надо будет удалить целый каталог и вы точно знаете что в нем нет открытых файлов. Используйте стандартную API функцию SHFileOperation, установив тип операции wFunc в FO_DELETE. Пример:
Только внимательно все проверьте! Будет обломно если ламерюга «вдруг» увидит надпись «Папка голые тетки не может быть удалена, так как юзер активно смотрит сразу 10 порно-фильмов из нее!».
А вообще, если опять удалиться в теорию, то наш главный объект Application имеет несколько полезных событий, наиболее интересное для нас OnException происходит тогда, когда в приложении возникает необработанная исключительная ситуация (деление на ноль, удаление несуществующего файла etc). По умолчанию, обработчик этого события вызывает метод ShowException для отображения окна сообщения с пояснением причины ошибки (оно нам надо?! конечно, НЕТ!).
Но, вы можете изменить реакцию на событие onException, переписав его обработчик. Тут ничего трудного нет, просто я не ставил перед собой задачу написать офигенный троян, только подтолкнуть вас к этому =) Кто захочет — допишет все необходимое сам, если что — я подскажу =) Ну вот, вроде бы все функции реализовали. Теперь немного о том, что мы не сделали =) Мы не сделали: загрузку/закачку файла. Это все можно сделать подправив SendFile(). Стандартных функций для пересылки файлов через TClient(Server)Socket я не нашел, но впрочем, это не проблема — просто передавайте файлы фрагментами.
Теперь нам осталось только разобраться с автозагрузкой трояна. Сделаем следующее:
1) при запуске файл переписывается в папку, в которой установлены винды.
2) прописываем запуск нашего файла в реестре.
В принципе, это все. Троян готов! Конечно, он тяжеловат, малофункционален etc, НО все это можно исправить, творите =) Если сделаете что-нибудь интересное — скидывайте, с радостью опубликуем это для всех. А вообще, я хочу сейчас все это переделать под API Socket’ы, без всяких компонентов, тогда и файл exe гораздо меньше станет. Если у кого-нибудь есть интерес к этому делу — напишите пару строк в мыльницу.
Товарищи ламеры! Внимательно следите теперь за соединениями по порту 4321 =) Некоторые ведь ничего не перекомпиливают и не меняют =)
Тогда какие библиотеки использовать?
Как написать программу которая удаленно управляет windows?
Представьте, что вам звонит мама и говорит, что у неё не открываются сайты. Настоящий айтишник отвечает так: «Не трогай компьютер, сейчас подключусь и всё починю». Вот как этому научиться.
Что за удалённый доступ
Где-то стоит компьютер. Вы подключаетесь к нему с помощью специального софта и получаете доступ.
В одном случае доступ — это возможность исполнять команды. О таком мы писали в статье про SSH: у вас на экране командная строка, вы можете выполнять команды на другом компьютере, но не увидите его экрана.
В другом случае может быть доступ к файловой системе — то есть можно копировать и записывать файлы. Так работает, например, FTP: когда вы подключаетесь к FTP-серверу, то фактически получаете удалённый доступ к части его файловой системы.
В третьем случае можно прямо увидеть картинку с монитора другого человека и поработать за компьютером так, будто вы сами за ним сидите. Такие системы называют VNC, Remote Desktop, «Общий экран», «Удалённый рабочий стол» и другими мудрёными словами.
Что нужно для удалённого доступа
Чтобы подключиться к другому компьютеру, нужно:
- установить на удалённый компьютер программу, которая отвечает за управление (серверная часть) — например TeamViewer;
- запустить у себя аналогичную программу (клиентская часть);
- убедиться, что оба компьютера могут выйти в интернет;
- найти другой компьютер через интернет — по IP-адресу, через серверный софт или как-то ещё.
Если интернет не работает на одном из компьютеров, соединиться не получится. Поэтому если вам звонят и говорят: «У меня интернет не работает, можешь как-то починить?», то удалённый доступ тут не поможет.
Для чего можно применить
О, вариантов масса.
Техподдержка. Понятное дело, можно оказывать техподдержку детям и родителям. Вы видите их монитор, нажимаете клавиши и исправляете всё, что нужно исправить. Не поможет, если нет интернета или не загружается компьютер как таковой.
Облачный терминал. Допустим, у вас на работе стоит супермощный компьютер с профессиональным софтом и огромной памятью. А дома — маломощный ноутбук, который годится только для интернета. И вот вам нужно срочно доделать какую-то работу на рабочем компьютере.
Не нужно ехать в офис: подключаетесь к нему удалённо, делаете дела, отключаетесь. Потеет рабочая машина, а ваш домашний ноутбук используется скорее как монитор.
Подобным образом сейчас устроены сервисы стриминга игр: где-то далеко стоит супермощный компьютер с мощной видеокартой, а вы подключаетесь к нему со своего простого. Запускаете «Ведьмака» и наслаждаетесь графикой. Но играете вы не на своём компьютере, а как бы на удалённом, там происходят все вычисления, а к вам через интернет лишь прилетает картинка.
Медиацентр. Например, у вас есть старый компьютер или мини-ПК. К нему подключена аудиосистема. На компьютере работает приложение для стриминга музыки или торрент-клиент, который обменивается легальной музыкой с другими пользователями.
Вам не нужно постоянно смотреть в монитор этого компьютера — он работает как бы фоном. Отлично: убираете от него монитор и клавиатуру, включаете удалённый доступ и подсоединяетесь к этому компьютеру, когда нужно.
Видеонаблюдение. К медиацентру можно подключить веб-камеру и подсоединяться к ней, когда вы в отпуске или на работе. Будет видно, чем занимается кот в ваше отсутствие.
Информационный экран. У вас в офисе может лежать ненужный мини-ПК или системный блок. Можно подключить его к большой плазме, сам комп спрятать куда-нибудь под стол, а плазму повесить повыше. Включаете комп, подключаетесь к нему дистанционно и настраиваете, что нужно выводить на плазме. Отключаетесь.
Комп показывает что надо на экране, шурша где-то в углу.
Файловый сервер. Старый системный блок или мини-ПК может отлично работать файловым сервером, а с помощью удалённого доступа можно настраивать его работу, управлять файлами и давать задание на скачку новых файлов. Он стоит себе на антресолях, а его жёсткие диски наполняются полезным контентом.
Секретная рабочая станция. Вариант для хакеров. Например, вы решили написать сверхсекретную программу. Арендуете виртуальный сервер где-нибудь в Ирландии или Корее. Ставите на него свою любимую операционную систему и сервер удалённого доступа.
Удалённо подключаетесь. Действуете так, как будто это ваш компьютер: пишете там код, тестируете, в общем — работаете как за обычным компьютером. А на своём домашнем ничего не храните.
Как только омоновцы начинают пилить вам дверь, отключаетесь от удалённого компьютера и стираете его адрес из памяти. Даже если эксперты попытаются извлечь ваш секретный код с изъятой техники, они ничего не найдут — ведь секретный код весь хранится на удалённом компьютере, адрес которого они не знают.
Что конкретно делать
Если у вас есть родители с компьютерами, установите им приложение TeamViewer и убедитесь, что оно работает: им выдаётся имя пользователя, приложение запускается и не вешает комп. Поставьте ярлык приложения на видное место, чтобы родители могли его найти в любой ситуации. На своём компьютере тоже поставьте TeamViewer.
Когда родителям потребуется компьютерная помощь, попросите их продиктовать вам код и пароль из их TeamViewer, введите его в своём TeamViewer, и у вас откроется их компьютер, будто вы сидите рядом.
Если у вас несколько компьютеров дома, установите на них какое-нибудь приложение с названием VNC Server, а на свой планшет — VNC Viewer. Теперь вводите во вьюер адреса ваших домашних компьютеров, и вы сможете управлять ими удалённо из другой комнаты. Адреса должны быть локальными, типа 192.168.0.2. О них ещё напишем.
Если у вас есть Mac Mini, отключите его от монитора и поставьте на антресоль. Теперь с любого другого Mac в доме зайдите в «Сеть» — Ваш Mac Mini — нажмите кнопку «Общий экран. ». Введите логин и пароль от Mac Mini, и у вас запустится встроенная в Mac OS система удалённого доступа.
Если используете VNC или доступ через общий экран Mac, помните о такой особенности: эти протоколы работают только тогда, когда два компьютера могут напрямую дозвониться друг до друга через интернет. Если вы захотите залезть в свой удалённый компьютер извне домашней сети, вам придётся прокладывать до него особый маршрут. Как это сделать, мы ещё расскажем, а если вкратце — это непросто.
Источник: kompter.ru
RDP over SSH. Как я писал клиент для удаленки под винду
Когда в кино показывают, как на Земле разражается эпидемия страшного вируса, людям там нужны в основном тушенка и патроны. Когда эта фантастика воплотилась в реальности, оказалось, что нужнее всего — туалетная бумага и удаленные рабочие места. И если с первым все понятно, второй пункт вызывает вопросы с точки зрения безопасности и выбора ПО. Когда готовое решение не подходит, нужно браться за сборку своего велосипеда.
Сервисы, с помощью которых можно организовать подключение сотрудников на удаленке, обычно работают через свои серверы. Почти всегда соединение получается медленнее прямых подключений, да и безопасность таких служб тоже под большим вопросом. Чаще всего архитектура этих решений построена вокруг реализации VNC (Virtual Network Computing).
Система базируется на протоколе RFB (Remote FrameBuffer). Управление устроено так: с одного компьютера на другой передаются нажатия клавиш и движения мыши и содержимое экрана ретранслируется через сеть. Сам VNC не шифрует передаваемые данные. Если требуется обеспечить повышенную безопасность, сессия может быть установлена через SSL, SSH или VPN-туннели, что несколько усложняет задачу.
На публичных сервисах все соединения устанавливаются через сервер, который выдает ID клиентов, подключая их либо через VPN напрямую друг к другу, либо через собственный канал. Конечно, можно поднять свой VPN-сервер, настроить маршрутизацию VPN-сети и локальной сети предприятия и подключать сотрудников по прямым IP-адресам. В этом случае клиенту нужно, помимо логина, пароля и адреса подключения, передать еще клиент VPN и данные для авторизации. Для некоторых пользователей все это сложновато, а это, в свою очередь усложнит поддержку.
Пробросить порты на локальные ПК тоже не вариант. Это небезопасно, да и настраивать маршрутизацию замучаешься, когда клиентов становится больше двух. В Linux есть замечательный клиент Remmina, который позволяет пробрасывать сессии RDP/VNC через SSH-соединение без дополнительных клиентов.
В Windows можно организовать SSH-туннели через клиентские приложения, которые необходимо настраивать на удаленных пользовательских машинах. SSH-клиент «из коробки» есть только в Windows 10, но как быть с юзерами семерки и восьмерки? Да и для Windows 10 придется писать батник, и не один. Все это не добавляет баллов стандартным решениям.
Но всегда можно придумать нестандартное. Чем мы прямо сейчас и займемся.
Постановка задачи
Итак, задача у нас будет следующая. Подключать пользователя по RDP (как выяснилось, это намного привычнее для большинства из них). Самое главное преимущество RDP по сравнению с VNC — это скорость. RDP быстрее потому, что этот протокол перерисовывает на стороне клиента только измененную часть экрана, а значит, данных передается меньше.
Подключение должно быть безопасным. Подключение должно выполняться с минимальными настройками и не требовать от пользователя никаких дополнительных действий.
А что скажет Google?
В общем‑то, задача не новая, и реализаций построения SSH-туннеля существует довольно много. В Google можно с ходу найти решения на базе PuTTY или варианты для Windows 10. Мы своих пользователей любим (и свои нервы тоже). А значит, надо дать им такой инструмент, который не нужно настраивать и который будет работать надежно.
Иными словами, решение должно отвечать следующим требованиям:
- простота для пользователя;
- легкость поддержки;
- простая и понятная подготовка и настройка «серверных частей».
Решение будет основано на технологии RDP over SSH. Технически мы организуем это так:
- клиент SSH для организации туннеля с пробросом порта до целевого ПК подключения;
- автоматический старт RDP-сессии без дополнительного ввода параметров подключения.
Система должна быть легко встраиваемой, и должен быть клиент для продвинутых пользователей (опционально).
Готовим серверную часть
Простые вещи вроде настройки RDP или SSH-сервера мы рассматривать не будем. Инструкций в интернете имеется тьма, а у нас объем ограничен, да и перегружать статью не хочется. Также я не стану подробно рассказывать, как реализовать получение данных с Kerio Control: на странице проекта можно найти готовый код.
Первым делом нам нужно разрешить RDP-подключения на клиентских машинах в локальной сети. Если там реализовано централизованное управление типа Active Directory, тебе повезло. Разрешаем подключение к RDP в групповых политиках. Если нет, обходим рабочие места ногами и разрешаем на целевых локальных машинах RDP. Не забываем и о файрволах.
Вторым шагом нам понадобится доступный из интернета SSH-сервер. Технически подойдет любое решение. Я использовал VPS с Debian 10 (один мой знакомый поднимал такой сервак даже на роутере, что небезопасно). Дальше стоит разделить решения на несколько версий, конкретная реализация зависит от того, как организовано получение данных для авторизации пользователей.
Первоначально у нас использовался Kerio с авторизацией пользователей через AD.
Клиент подключался по SSH, пробрасывал порт на API Kerio Control Server, затем подключался к нему, выполнял поиск по заданным параметрам (логин или фамилия сотрудника), искал IP локального ПК. Далее разрывал SSH-соединение и устанавливал новое уже с пробросом порта на найденный IP, на порт RDP (3389), после чего штатными средствами Windows поднималась сессия RDP с передачей параметров подключения.
Такое решение работало довольно быстро, но нам этого стало мало, и мы разделили его на две части. Серверный скрипт стал работать на SSH-сервере и сам время от времени ходить в Kerio за информацией. Клиентская часть подключалась к серверу по SFTP, искала нужные данные, оформленные в JSON, и выполняла подключение. В итоге скорость работы увеличилась.
Рекомендую сразу настроить сервер OpenSSH с доступом по ключам и подготовить RSA-ключи для авторизации. Для этого нужно создать отдельного пользователя и ограничить его в правах, затем отдать ему публичную часть ключа. Ниже приведу часть / etc/ ssh/ sshd_config с настройками этих двух пользователей:
Источник: xakep.ru
Saved searches
Use saved searches to filter your results more quickly
Cancel Create saved search
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window.
Reload to refresh your session.
Simple RDP client-server application written in c++ using Qt and WinAPI
DeliriumV01D/Helper
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Switch branches/tags
Branches Tags
Could not load branches
Nothing to show
Could not load tags
Nothing to show
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Cancel Create
- Local
- Codespaces
HTTPS GitHub CLI
Use Git or checkout with SVN using the web URL.
Work fast with our official CLI. Learn more about the CLI.
Sign In Required
Please sign in to use Codespaces.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching Xcode
If nothing happens, download Xcode and try again.
Launching Visual Studio Code
Your codespace will open once ready.
There was a problem preparing your codespace, please try again.
Latest commit
Git stats
Files
Failed to load latest commit information.
Latest commit message
Commit time
README.md
Simple RDP client-server application written in c++ using Qt and WinAPI
Программа «Помощник» состоит из трех частей, являющихся отдельными программами
Server — отладочный http-сервер для связи Sharer и Viewer
Sharer — Программа клиента. Предназначена для создания RDP-сесии, генерации приглашения и отправки приглашения на сервер для последующей передачи его программе специалиста.
Viewer — Программа специалиста. Умеет запрашивать с сервера приглашение для подключения к клиенту. Подключается к клиенту с целью управления посредством RDP.
Все программы написаны на языке с++ в Microsoft Visual Studio 2010 с использованием библиотеки Qt
- Sharer Верхний класс в иерархии TSharer — пользовательский интерфейс (модули TSharer.h, TSharer.cpp) наследуется от QMainWindow, TSharer, TRDPServer Содержит в качестве делегата THelperClient
TRDPServer (модули TSharer.h, TSharer.cpp) — Класс для обеспечения доступа к данному компьютеру через RDP Открытие/зыкрытие сессии, генерация приглашения, получение событий от RDP-клиента.
THelperClient (модули THelperClient.h, THelperClient.cpp) — общая часть в программах клиента и специалиста для общения с HTTP-сервером по протоколу.
Программа такая разнородная, потому что начал писать ее с нуля, не найдя никаких готовых библиотек. Но потом все таки нашел RDP.h, RDP.cpp, но для экономии времени работающий кусок было решено не переписывать заново.
RDP.h и RDP.cpp — набор классов, являющихся оберткой вокруг WindowsAPI функций для работы с RDP.
TSharer.ui — GUI форм, пока не используется. (вся работа через трей)
- Viewer класс Viewer (модули Viewer.h и Viewer.cpp) наследуется от QMainWindow и Ui::ViewerClass в качестве делегата имеет THelperClient
Вся его работа сейчас крутится вокруг расположенного на визуальной форме ActiveX компонента axRDPClientWidget обеспечивающего функционал RDP — клиента.
Визуальный форм — Viewer.ui
- Server Консольная программа.
Основной функционал заключен в классе THelperServer(модули THelperServer.h, THelperServer.cpp) В нем переопределены методы readClient() для получения данных с клиента и ProcessRequest() для обработки клиентских запросов и генерации ответов сервера. Класс THelperServer унаследован от класса THTTPServer
THTTPServer (модули THTTPServer.h, THTTPServer.cpp) унаследован от кутэшного QTcpServer
В модуль THelperProtocol.h планируется вынести парсеры клиентских и серверных запросов, чтобы можно было использовать в разных программах.
- PostConnectionString (c->s)
Источник: github.com