CS8803: Exploiting Smart Contract and DeFi

Taesoo Kim



CS8803: Solidity 101

Taesoo Kim

Recap

This Week’s Study

Let’s Implement Some Contracts!

Ethereum Layers

Ref. EI §2

Ethereum Virtual Machine (EVM)

Ref. EI §2

EVM Architecture

Ref. EI §2

EVM: Memory Spaces

Ref. EI §2

EVM: Stack

Ref. EI §2

Example: Stack

PUSH1 0x80
--------------------------------------------------

stack  : []
memory : []
stroage: {}

stack  : [0000000000000000000000000000000000000000000000000000000000000080]
memory : []
stroage: {}

EVM: Memory

Ref. EI §2

Example: Memory

MSTORE 0x40 0x80
--------------------------------------------------

stack  : [
 0000000000000000000000000000000000000000000000000000000000000080,
 0000000000000000000000000000000000000000000000000000000000000040
]
memory : []
stroage: {}

stack  : []
memory : [
 0000000000000000000000000000000000000000000000000000000000000000,
 0000000000000000000000000000000000000000000000000000000000000000,
 0000000000000000000000000000000000000000000000000000000000000080
]
stroage: {}

EVM: Account Storage

Ref. EI §2

Example: Account Storage

SSTORE 0x00 0xdeadbeef
--------------------------------------------------

stack  : [
 00000000000000000000000000000000000000000000000000000000deadbeef,
 0000000000000000000000000000000000000000000000000000000000000000
]
memory : []
stroage: {}

stack  : []
memory : []
stroage: {
 0000000000000000000000000000000000000000000000000000000000000000: 
   00000000000000000000000000000000000000000000000000000000deadbeef
}

EVM Code

Ref. EI §2

Example: EVM Code

> web3.eth.get_code(s.address)
'0x6080604052348015600f57600080fd5b506004361060...'

> print(evm_disasm(web3.eth.get_code(s.address)))
00000: PUSH1 0x80
00002: PUSH1 0x40
00004: MSTORE
00005: CALLVALUE
00006: DUP1
00007: ISZERO
00008: PUSH1 0x0f
0000a: JUMPI
0000b: PUSH1 0x00
0000d: DUP1
0000e: REVERT
0000f: JUMPDEST
...

EVM Execution Model

Ref. EI §2

Message Call

Ref. EI §2

Instructions for Message Call

Ref. EI §2

Example: Call Instruction

Example: Call

contract Caller {
    function storeToStorage(address addr, uint256 num) external {
        Storage(addr).store(num);
    }
}
> s = Storage.deploy()
> c = Caller.deploy()
> tx = c.storeToStorage(s.address, 0xdeadbeef)
> tx.call_trace()
Call trace for '0x7e4ab5ee285ce7a35451ec523e4291eb2...':
Initial call cost  [21608 gas]
Caller.storeToStorage  0:192  [1142 / 2854 gas]
└── Storage.store  [CALL]  118:177  [1712 gas]

Example: Call

> tx.trace[118-1:118+1]
[{
'fn': "Caller.storeToStorage",
'memory': [
  0x80: "6057361d00000000000000000000000000000000000000000000000000000000",
  0xa0: "deadbeef00000000000000000000000000000000000000000000000000000000"],
'gasCost': 187834,
'op': "CALL",
'stack': [
  "0000000000000000000000000000000000000000000000000000000000000000",<retSize>
  "0000000000000000000000000000000000000000000000000000000000000080",<retOffset>
  "0000000000000000000000000000000000000000000000000000000000000024",<argSize>
  "0000000000000000000000000000000000000000000000000000000000000080",<argOffset>
  "0000000000000000000000000000000000000000000000000000000000000000",<value>
  "0000000000000000000000003194cbdc3dbcd3e11a07892e7ba5c3394048cc87",<address>
  "0000000000000000000000000000000000000000000000000000000000b6c24e" <gas>
]},

Example: New Execution Context

... (cont)
{
  'address': "0x3194cBDC3dbcd3E11a07892e7bA5c3394048Cc87",
  'fn': "Storage.store",
  'memory': [],
  'stack': [],
  'storage': {}
  'op': 'PUSH1',
  'pc': 0,
  'gas': 0xb3e494,
}]

Gas and Fee

Ref. EI §2

Gas

Ref. EI §2

Example: Gas (Static)

Example: Gas (Dynamic)

static_gas = 0

if value == current_value
    if key is warm
        base_dynamic_gas = 100
    else
        base_dynamic_gas = 100
else if current_value == original_value
    if original_value == 0
        base_dynamic_gas = 20000
    else
        base_dynamic_gas = 2900
else
    base_dynamic_gas = 100

Ref. SSTORE

Input and Output of EVM

Ref. EI §2

Instructions for Input Data

Ref. EI §2

Example: Loading a Function Selector

CALLDATALOAD 0x00
--------------------------------------------------

stack  : [
 0000000000000000000000000000000000000000000000000000000000000000
]
memory : []
stroage: {}

stack  : []
memory : [6057361d00000000000000000000000000000000000000000000000000000000]
stroage: {}

Contract Creation

> s = Storage.deploy()

# deployment code. [] indicates the contract's code
> Storage.bytecode
'6080604052348015600f57600080fd5b5060ac8061001e6000396000f3fe[6080604052348015600f57600080fd5...]'
# +-----------------------------------------------------------
# => code that deploys the contract as a payload

# contract code
> web3.eth.get_code(s.address)
'[6080604052348015600f57600080fd5...]'

Example: Contract Creation

Ref. EI §2

> tx = web3.eth.sendTransaction({"from": a0, "data": Storage.bytecode})
> web3.eth.getTransactionReceipt(tx)
{contractAddress: '0xcCB53c9429d32594F404d01fbe9E65ED1DCda8D9', ...}

Reading Contract Deployment Code

> print(evm_disasm(Storage.bytecode))
00000: PUSH1 0x80
00002: PUSH1 0x40
00004: MSTORE
00005: CALLVALUE
00006: DUP1
00007: ISZERO
00008: PUSH1 0x0f
0000a: JUMPI
...

Decompilation of Contract Deployment Code

contract Contract {
    function main() {
        memory[0x40:0x60] = 0x80;

        var var0 = msg.value;
        if (var0) { revert(memory[0x00:0x00]); }
    
*       memory[0x00:0xac] = code[0x1e:0xca];
*       return memory[0x00:0xac];
    }
}

Examine Payload

> HexBytes(s.bytecode) == web3.eth.get_code(s.address)
True

> HexBytes(Storage.bytecode)[0x1e:0xca] == web3.eth.get_code(s.address)
True

→ Read the trace of CODECOPY and RETURNin the tutorial!

Deployment Code

# codecopy(destOffset=0x00, offset=0x1e, size=0xac)
0011    60  PUSH1 0xac
0013    80  DUP1
0014    61  PUSH2 0x001e
0017    60  PUSH1 0x00
0019    39  CODECOPY

# return(offset=0x00, size=0xac)
001A    60  PUSH1 0x00
001C    F3  *RETURN

Deployment Code in pwn.py

    code = HexBytes("0xdeadbeef")
    deployer = evm_asm("""\
PUSH1 %d
DUP1
PUSH1 0x0b
PUSH1 0x00
CODECOPY
PUSH1 0x00
RETURN
""" % len(code))

    tx = web3.eth.send_transaction({"from": a[0].address, "data": d + code})
    tx = web3.eth.get_transaction_receipt(tx)

    # what's here?!
    web3.eth.get_code(tx.contractAddress)

Today’s Tutorial

  1. Write a contract in assembly that sends 1 gwei back to you when invoked
  2. Your contract should be less than 0x20 bytes!

Lab03!

References