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++, PHP, 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
); // Event Call
}

function removeAdmin (address _address) {
if (owner.addr != msg.sender || !isAdmin[_address]) throw;

isAdmin[_address] = false;
adminRemoved(
_address,
adminInfo[_address].name,
adminInfo[_address].desc
); // Event Call
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. Але якщо є якісь пропозиції — обов'язково пишіть в коментарі.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.