Taesoo Kim
Taesoo Kim
pwn.py
, Solidity DecompilerRef. EI §2
Ref. EI §2
Ref. EI §2
input
field of the transaction?Ref. EI §2
>>> s = Storage.deploy()
>>> tx = s.store(0xdeadbeef)
>>> web3.eth.get_transaction(tx.txid)
hash : 0x50fee6c148904dd7dd5c7017f79bc4396aa35007b677ebcce250aa58c530e967,
nonce : 11,
blockHash : 0xadf3166bedf07267d3d31113e3b8b616164ff530534f77a4570d8b210899fb31,
blockNumber : 12,
transactionIndex : 0,
from : 0x66aB6D9362d4F35596279692F0251Db635165871,
to : 0xb6286fAFd0451320ad6A8143089b216C2152c025,
value : 0,
gas : 12000000,
gasPrice : 200000000,
* input :
* 0x6057361d00000000000000000000000000000000000000000000000000000000deadbeef,
v : 2710,
r : 0xf8d77b2bc96345c2c9a91f55cca6487af2af303c98885bcfc1f5e8dff0dabd7a,
s : 0x6d77c32a0db4d5a9a1773c9486ecd4ca2353db3b9b66790414b64c14c755108b,
> 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
...
store(0xdeadbeef)
is encoded in this
following form (below):[4-byte function selector][a list of arguments]
1) function selector: web3.keccak(text="store(uint256)")[:4] == 0x6057361d
2) appending a list of arguments
e.g., input:
[0x6057361d][00000000000000000000000000000000000000000000000000000000deadbeef]
+---------- +---------------------------------------------------------------
| |
+-> 4 bytes +-> 0x20 bytes (uint256 num)
function main() {
// 1. what's this for?
var var0 = msg.value;
if (var0) { revert(memory[0x00:0x00]); }
// 2. what's this for?
if (msg.data.length < 0x04) { revert(memory[0x00:0x00]); }
// 3. what's this for?
var0 = msg.data[0x00:0x20] >> 0xe0;
if (var0 == 0x2e64cec1) {
// Dispatch table entry for retrieve()
} else if (var0 == 0x6057361d) {
// Dispatch table entry for store(uint256)
} else { revert(memory[0x00:0x00]); }
}
function submitProofOfCollision(string memory func2) external {
string memory func1 = "callMeIfYouCan(uint256,bool)";
uint hash1 = keccak256(abi.encodePacked(func1));
uint hash2 = keccak256(abi.encodePacked(func2));
require(hash1 != hash2, "func1 != func2")
require(hash1 >> (256 - 32) == hash2 >> (256 - 32),
"Find two functions that have the same selector");
completed1 = true;
}
constructor(args)
is encoded a part of the contract
creation transaction!input = [deployer+constructor][bytecode][args]
// primitive types
uint weight;
bool voted;
string name;
bytes data;
address chairperson;
// data structure
mapping(address => Voter) voters;
Proposal[] proposals;
Ref. EI §2
contract A {
uint public x; // -> storage @slot-0 (key=0)
uint256 public y; // -> storage @slot-1 (key=1)
uint128 public z0; // -> storage @slot-2 (key=2)
uint8 public z1; // -> storage @slot-?
uint16 public z2; // -> storage @slot-?
bool public b; // -> storage @slot-?
S public s; // -> storage @slot-?
address public addr;// -> storage @slot-?
...
}
Ref. EI §2
>>> src = open("contract/A.sol").read()
>>> dump_layout(src)
[00]@000-031 x : I/032B uint256
[01]@000-031 y : I/032B uint256
[02]@000-015 z0 : I/016B uint128
[02]@016-016 z1 : I/001B uint8
[02]@017-018 z2 : I/002B uint16
[02]@019-019 b : I/001B bool
...
size = len(bytes)*2
(discussed later)# when size <= 31B, inplace encoding. See, one byte size, 0x06/2 = 3B.
>>> a.set_b1(0xaabbcc)
>>> a.get_storage_at(12)
'0xaabbcc0000000000000000000000000000000000000000000000000000000006'
(size) --
# size == 31B, 0x3e/2 = 31B
>>> a.set_b1(2**(256-8)-1)
>>> a.get_storage_at(12)
'0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3e'
(size) --
keccak256(#slot)
and the size is stored at #slotsize = len(bytes)*2 + overflowed
# if the data is bigger than 32B, it uses keccak256(12) + N
# where N == size // 32
>>> a.set_b1(2**400-1)
>>> a.get_storage_at(12)
'0x0000000000000000000000000000000000000000000000000000000000000065'
>>> a.get_storage_at(web3.solidityKeccak(["uint256"], [12]))
'0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
>>> a.get_storage_at(web3.solidityKeccak(["uint256"], [12]).int()+1)
'0xffffffffffffffffffffffffffffffffffff0000000000000000000000000000'
#slot
contains the size of the dynamic arraykeccak(#slot) + index
keccak256(#slot) + index
# two elements in the array declared at slot 10
>>> a.get_storage_at(10)
'0x0000000000000000000000000000000000000000000000000000000000000002'
>>> a.get_storage_at(web3.solidityKeccak(["uint256"], [10]).int()+0)
'0x00000000000000000000000000000000000000000000000000000000deadbeef'
>>> a.get_storage_at(web3.solidityKeccak(["uint256"], [10]).int()+1)
'0x0000000000000000000000000000000000000000000000000000000000c0ffee'
>>> a.get_storage_of_array(10, element=2)
[0xc65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a8] len=2
[00] 0x00000000000000000000000000000000000000000000000000000000deadbeef
[01] 0x0000000000000000000000000000000000000000000000000000000000c0ffee
struct S {
uint128 a; // 1. what's size?
uint128 b; // 2. what's size?
uint[2] staticArray; // 3. what's size?
uint[] dynArray; // 4. so in total?
}
S public s; // -> storage @slot-?
address public addr;// -> storage @slot-?
>>> dump_layout(src)
[03]@000-127 s : I/128B struct A.S
[07]@000-019 addr : I/020B address
...
struct A.S:
[00]@000-015 a : I/016B uint128
[00]@016-031 b : I/016B uint128
[01]@000-063 staticArray : I/064B uint256[2]
[03]@000-031 dynArray : D/032B uint256[]
map[key] = value
is stored at
keccak256(key . #slot)
map[key] = value
is stored at
keccak256(key . #slot)
key
, which likely
controllable by an attack!key
, which likely
controllable by an attack!