Pull to refresh

Malware + Blockchain = ❤️

Reading time 6 min
Views 28K

Эта статья является продолжением цикла о написании умных контрактов на платформе 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. Но если есть какие-то предложения — обязательно пишите в комментарии.

Tags:
Hubs:
+22
Comments 5
Comments Comments 5

Articles