Одна строка кода испарила 6 447 277 680 йен!
Когда вы входите сейчас, вы по-прежнему являетесь первым, кто двигается, а последним, кто смотрит, является лук-порей.
Цай Вэньшэн, председатель Meitu, однажды громко произнес эти слова в трехчасовой группе, и они сразу же распространились среди публики.
Вскоре после того, как он сделал замечание, 3-я американская сеть (BEC) вырастет на 4000%, а затем резко упадет. Хотя он неоднократно отрицал, что умный пользователь сети уже дал пощечину, он и BEC 千 万 万 万 万
Дилер контролировал цену валюты, и цена акций Meitu взлетела до небес.Цай Вэньшэн успешно выполнил свой план по сбору лука-порея.
Но в валютном кругу те, кто режет людей, всегда будут их резать.
При взрыве уязвимости интеллектуального контракта BEC он взламывается, а мгновенный пакет продается за большое количество BEC, а 600 миллионов — это ноль в одно мгновение.
И все это оказалось из-за очень простого программного бага.
задний план
Кто-то сказал сегодня в группе,Сеть салонов красотыВ коде есть ошибка, и кто-то использовал ее для получения 57 896 044 618 658 100 000 000 000 000 000 000 000 000 000 000 000 000 000 000,792003956564819968 BEC.
Эта запись операции0xad89ff16fd1ebe3a0a7cf4ed282302c06626c1af33221ebe0d3a470aba4a660f
Теперь позвольте мне показать вам, как это делают хакеры!
Мы видим, что выполняемый методbatchTransfer
Так для чего этот метод? (Отправить такое же количество токенов на указанные адреса)
Общая логика такая
Вы присылаете мне несколько адресов (_receivers), а затем сколько токенов вы хотите дать каждому человеку (_value)
Затем общая сумма, которую вы хотите отправить = количество людей отправлено * сумма отправлена
Затем попросите, чтобы ваш текущий баланс был больше, чем общая отправленная сумма.
Затем вычтите общую сумму, которую вы отправили
Затем отправьте указанную сумму (_Value) каждому в _receivers
С логической точки зрения здесь нет проблем, вы хотите отправить токены другим, то у вас есть баланс должен быть больше, чем общая сумма, отправленная!
Однако этот код совершил глупую ошибку!
объяснение кода
Этот метод будет передавать два параметра
- _receivers
- _value
Значение _receivers — это список с двумя адресами в нем.
0x0e823ffe018727585eaf5bc769fa80472f76c3d7
0xb4d30cac5124b46c2df0cf3e3e1be05f42119033
Значение _value равно8000000000000000000000000000000000000000000000000000000000000000
Давайте снова посмотрим на код (как показано ниже)
Давайте объясним один за другим
uint cnt = _receivers.length;
Для получения _recevers есть несколько адресов, мы видим, что из приведенного выше адреса только два параметра есть только два параметра, поэтому CNT = 2, который отправляется двумя адресам токена
uint256 amount = uint256(cnt) * _value;
uint256
во-первыхuint256(cnt)
Является ли преобразование cnt в тип uint256
Итак, что такое тип uint256? Или каков диапазон значений типа uint256...
Диапазон значений типа uintx — от 0 до 2, возведенных в степень x-1.
То есть если это uint8
Затем диапазон значения UINT8 составляет от 0 до 2 до 8-й Power -1
то есть от 0 до 255
Тогда диапазон значений uint256 равен
0 - 2 в 256 степени -1 есть0 到115792089237316195423570985008687907853269984665640564039457584007913129639935
python вычисляет 2 в 256-й степени
Так что, если установленное значение превышает диапазон значений? Эта ситуация называется溢出
Привести пример, чтобы проиллюстрировать
Поскольку значение uint256 слишком велико, в качестве примера используется uint8. . .
Из вышеизложенного мы уже знаем, что минимальное значение uint8 равно 0, а максимальное значение равно 255.
Итак, когда я получаю 255 + 1, каков результат?результат будет 0
Итак, когда я получаю 255 + 2, каков результат?результат станет 1
Итак, каков результат, когда я получаю 0 - 1?Результат станет 255
Итак, каков результат, когда я получаю 0 - 2?Результат станет 255
Итак, мы возвращаемся к коду выше,
amount = uint256(cnt) * _value
тогда сумма = 2* _value
Но в это время _value шестнадцатеричное, мы конвертируем его в десятичное
(python шестнадцатеричный в десятичный)
Вы можете видеть _value =57896044618658097711785492504343953926634992332820282019728792003956564819968
тогда сумма = _value*2 =115792089237316195423570985008687907853269984665640564039457584007913129639936
Вы можете видеть выше, что максимальный диапазон значений uint256 составляет115792089237316195423570985008687907853269984665640564039457584007913129639935
В этот момент amout превысил максимальное значение, и переполнение равноamount = 0
следующая строка кодаrequire(cnt > 0 && cnt <= 20);
Оператор require означает, что оператор должен быть правильным, то есть cnt должен быть больше 0 и меньше или равен 20.
Наш CNT равен 2, проходят!
require(_value > 0 && balances[msg.sender] >= amount);
Это предложение требует, чтобы _value было больше 0, наше _value больше 0 Более того, баланс токена текущего пользователя больше или равен сумме, потому что сумма равна 0, так что даже если у вас нет токена, вы довольны!
balances[msg.sender] = balances[msg.sender].sub(amount);
Это предложение баланс текущего пользователя - сумма
Текущая сумма равна 0, поэтому текущий баланс токена пользователя не изменился.
for (uint i = 0; i < cnt; i++) {
balances[_receivers[i]] = balances[_receivers[i]].add(_value);
Transfer(msg.sender, _receivers[i], _value);
}
Это предложение должно пройти по адресу в _receivers, Сделайте следующее для каждого адреса
balances[_receivers[i]] = balances[_receivers[i]].add(_value);
Баланс адреса в _receivers = исходный баланс + значение
Таким образом, баланс адреса в _receivers добавляет 57896044618658097711785492504343953926634992332820282019728792003956564819968 токенов! ! !
Transfer(msg.sender, _receivers[i], _value); }
这句则只是把赠送代币的记录存下来! ! !
Суммировать
Простая уязвимость переполнения привела к тому, что рыночная стоимость токенов BEC приблизилась к нулю.
Итак, разработчики рассмотрели проблему переполнения?
На самом деле он считал
Вы можете увидеть скриншот выше
Помимо расчета суммы, метод safeMath (sub, add) используется для перевода денег пользователю.
Так почему же в этом предложении не используется метод safeMath? . .
Это зависит от того, кто пишет код. . .
Что безопасноМатематика
safeMath — это библиотека, написанная для обеспечения безопасности вычислений.
Посмотрим, что он делает Почему мы можем гарантировать компьютерную безопасность?
function mul(uint256 a, uint256 b) internal constant returns (uint256) {
uint256 c = a * b;
assert(a == 0 || c / a == b);
return c;
}
Умножение, как указано выше. После вычисления он использовал assert, чтобы проверить правильность результата!
Если mul используется при расчете суммы выше,
ноc / a == b
То есть проверить, что сумма /cnt == _value
Фраза выдает ошибку, потому что 0/cnt не означает _value
Так программа сообщит об ошибке!
Перелива не будет...
Ну, есть небольшая проблема, вотassert
хорошоrequire
Кажется, делает то же самое
Все для проверки правильности утверждения!
Так в чем же между ними разница?
Если используется assert, будет израсходован лимит газа программы.
Для запроса он просто потребляет исполняемый в данный момент газ.
Суммировать
Итак, как нам избежать этой проблемы?
Моё личное мнение такое
- Всякий раз, когда дело доходит до расчетов, обязательно используйте safeMath
- Код должен быть протестирован!
- Код должен быть пересмотрен!
- При необходимости попросите компанию, специализирующуюся на аудите кода, протестировать код.
Что мне делать после этого случая?
В настоящее время этот метод приостановлен (к счастью, может быть приостановлен), поэтому друзья, прочитавшие статью, не тестируют его...
Но случилось то, что мы должны были сделать?
Затем выпустите новый токен и отправьте такое же количество токенов предыдущим пользователям...