Malware + Blockchain = ❤️

от автора

Эта статья является продолжением цикла о написании умных контрактов на платформе Ethereum. В первой части я пообещал показать, как создать новую криптовалюту на Solidity (в мире блокчейна это является чем-то вроде аналога "Hello, world!"). Но на самом деле в этом нет смысла, так как об этом уже написано несколько хороших статей (пример из доков Solidity, пример с главной страницы Ethereum).

Так что я немного подумал и нашел еще один use case для умных контрактов. В данной статье я покажу, как теоретически автор трояна-шифровальщика может монетизировать свое детище, продавая ключи для расшифровки с помощью умных контрактов.

BTW все написанное ниже имеет чисто образовательный характер.

Общая идея

Шифровальщики появились не вчера и имеют более-менее схожую схему работы. И как правило в этой схеме присутствуют шаги вида

  • Оплата выкупа через *coin
  • Отправка некоторого ID зараженного ПК + ID транзакции преступникам
  • Получение ключа для расшифровки файлов

Вот эти три фрагмента системы мы и попытаемся перенести в блокчейн.

Общая структура проекта

Наш проект будет состоять из двух частей — модуль администрирования и модуль "магазина". Админку мы сделаем в виде отдельного контракта, а контракт магазина просто от него унаследуем. Вообще говоря, в Ethereum можно взаимодействовать между двумя различными контрактами в блокчейне, достаточно лишь знать их адреса и названия интересующих нас функций, но это я продемонстрирую как-нибудь в следующий раз.

Tool box

Писать будем на Solidity версии 0.4.2 (текущая версия на 26 октября 2016). В качестве среды разработки можно использовать онлайн компилятор или только что вышедшую онлайн платформу Ethereum studio. Последняя сделана на базе c9.io, но с фичами для разработки под Ethereum. Сам не пользовался, так как вышла только-только, но выглядит симпатично, хотя документацию ее создатели прячут, наверное, специально.
В качестве клиента-кошелька возьмем Mist, а так как мы делаем просто PoC, то и запускать все контракты будем на своем private блокчейне (как это сделать я рассказывал здесь). Так будет проще, дешевле (в смысле бесплатно) и быстрее.

Пишем код

Для начала создадим модуль администрирования. В него мы добавим функционал для добавления и удаления администраторов, вывода денег и "убийства контракта". Первым делом определим все необходимые переменные и функцию-конструктор. Она должна называться так же как и сам контракт и вызывается лишь однажды (автоматически) — при загрузке контракта в блокчейн.

pragma solidity ^0.4.2; // Указываем версию языка - любая, начиная с 0.4.2 до 0.5 не включительно  contract admin {      // VARIABLES      struct user {           address addr;           string name;  // '$uPeR_p0wner_1999'           string desc;  // 'CEO & CTO'      }       user owner;      mapping (address => user) adminInfo;      mapping (address => bool) isAdmin;       function admin (string _name, string _desc) {           owner = user({                addr : msg.sender, // msg - дефолтная переменная с информацией о пользователе                name : _name,      // вызвавшем контракт. msg.sender - его адрес                desc : _desc       // msg.value - сумма в wei, переданная контракту и т.д.           });            isAdmin[msg.sender] = true;           adminInfo[msg.sender] = owner;      } }

Сам по себе код прост и понятен, благо синтаксис напоминает C++, JS, C, etc. На всякий случай напомню, что оператор struct позволяет создавать кастомные типы данных из уже имеющихся . Mapping, как можно догадаться реализует ассоциативный массив (dict в Python, map в C++).

Здесь мы создали переменную owner, в которой с помощью struct храним адрес, имя и какой-нибудь description для создателя контракта. Контракты в Ethereum имеют так называемый state, то есть в дальнейшем, когда кто-то вызовет контракт, мы сможем воспользоваться этой переменной.

Далее добавим функции, отвечающие за добавление / удаление администратора, вывод денег и уничтожение контракта. Здесь все вообще тривиально, кроме одной штуки — оператора event. Это очень симпатичный, с точки зрения UI и юзабилити вообще, оператор, который позволяет реализовать что-то вроде push уведомлений внутри контракта. Чуть нижу будет скриншот, из которого понятно, как это выглядит на практике.

          // EVENTS      event adminAdded(address _address, string _name, string _desc);      event adminRemoved(address _address, string _name, string _desc);      event moneySend(address _address, uint _amount);       // FUNCTIONS      function addAdmin (address _address, string _name, string _desc) {           if (owner.addr != msg.sender || isAdmin[_address]) throw;    // Только владелец может добавлять / удалять админов            isAdmin[_address] = true;           adminInfo[_address] = user({addr : _address, name : _name, desc : _desc});            adminAdded(               _address,               _name,               _desc           ); // Call event      }       function removeAdmin (address _address) {           if (owner.addr != msg.sender || !isAdmin[_address]) throw;            isAdmin[_address] = false;           adminRemoved(               _address,               adminInfo[_address].name,               adminInfo[_address].desc           ); // Call event           delete adminInfo[_address];      }       function getMoneyOut(address _receiver, uint _amount) {           if (owner.addr != msg.sender || _amount <= 0 || this.balance < _amount) throw;           // Функцию может вызвать только владелец, требуемая сумма должна быть положительна           // Последняя проверка - баланс контракта должен быть больше требуемой суммы            if (_receiver.send(_amount)) moneySend(_receiver, _amount); // В случае успеха - вызвать event      }       function killContract () {           if (owner.addr != msg.sender) throw;           selfdestruct(owner.addr); // Все средства на счету контракта будут переведены на адрес владельца      }

Весь этот код просто добавим внутри contract admin {…} после уже написанного и наш модуль для администрирования готов.

Заливаем в блокчейн и наслаждаемся результатом

Этот шаг довольно подробно описан в первой части, не буду на нем останавливаться. Приложу лишь несколько скриншотов работы с уже готовым контрактом. Вот так например в Mist выглядит вызов функции добавления админа:

А вот так выглядят обещанные event-ы:

Магазин

Сначала суть: мы просто сделаем очередь из уже оплаченных заявок на получение ключа. В нашем случае администраторы будут разгребать эту кучу руками (можно автоматизировать, но опять же — как нибудь в следующий раз) и добавлять для каждой заявки ключ в импровизированную БД (сделаем map вида _id -> _key). ID, для простоты, у нас будет натуральным числом, а ключом будет string (например ссылка на pastebin).

Сам код поместился в 85 строк, вот он:

contract shop is admin {     // VARIABLES     uint[] orders; // Очередь из оплаченных заказов     uint currentOrder = 0; // Номер последнего необработанного заказа     mapping (uint => string) keys; // Пары ID - ключ      // EVENTS     event keyAdded(uint _ID, string _name, string _desc);     event keyBought(address _address, uint _ID);      // FUNCTIONS     function buyKey(uint _ID) payable { // Без модификатора payable на функцию нельзя отправлять эфир         if (msg.value < 15000000000000000000) throw; // Проверяем, что пользователь отправил нам минимум 15 этеров         orders.push(_ID); // Добавляем его в массив оплаченных заказов          keyBought(             msg.sender,             _ID         );     }      function getKeyByID(uint _ID) returns (string) { // Таким специфическим образом указывается, что вернет функция         return keys[_ID]; // Если ключ для этого ID еще не добавлен, то вернется пустая строка     }      function getLastOrder() returns (uint) {         if (!isAdmin[msg.sender]) throw;         return orders[currentOrder]; // Возвращаем первый ID         currentOrder += 1;     }      function addKey(uint _ID, string _key) {         if (!isAdmin[msg.sender]) throw; // Только администратор может добавить ключ для какого-то ID         keys[_ID] = _key;          keyAdded(             _ID,             adminInfo[msg.sender].name,             adminInfo[msg.sender].desc         );     } }

Итог

Еще раз подчеркну, что написанное здесь — это прототип с кучей погрешностей и недоделок. Простой пример — в нашем случае массив заявок никак не чистится и только набирает в размере. Из-за этого, когда-нибудь стоимость вызова функции buyKey вырастет до стоимости самого ключа, что как-то неправильно.

Другой, более сложный момент — для того, чтобы хранить порядковый номер последнего обработанного заказа, мы используем переменную currentOrder. А теперь представим ситуацию — есть два админа: Вася в Пекине и Петя в Нью-Йорке. В один момент времени они обратились к функции getLastOrder и оба получили какой-то номер — пусть 23412. Далее каждый из них вызвал функцию addKey и добавил в "базу" ключ для этого заказа, а вместе с ним сохранился его name и desc. В результате, когда майнеры начинают выполнять эти действия, те что поближе к Пекину, быстрее выполнят Васин запрос и state будет иметь один вид, а те что поближе к Нью-Йорку — Петин и state получится другой. В результате какой-то merge conflict.

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

В следующей статье скорее всего напишу, как прикручивать к контрактам интерфейсы отличные от Mist (например взаимодействие через обычный сайт) ну или как работать с Ethereum в связке с каким-нибудь языком программирования, например Python. Но если есть какие-то предложения — обязательно пишите в комментарии.

ссылка на оригинал статьи https://habrahabr.ru/post/313710/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *