Skip to content

06. Delegate

r1oga edited this page Oct 28, 2022 · 1 revision

Target

Claim ownership of the contract.

Weakness

The Delegation fallback implements a delegatecall.
By sending the right msg.data we can trigger the function pwn() of the Delegate contract.
Since this function is executed by a delegatecall the context will be preserved:
owner = msg.sender = address of contract that send data to the Delegation fallback (attacker contract)

Solidity Concepts

Storage, call another contract's function

There are several ways to interact with other contracts from within a given contract.

If ABI available

If the ABI (like an API for smart contracts) and the contract's address are known, we can simply instantiate (e.g with a contract interface) the contract and call its functions.

contract Called {
	 function fun() public returns (bool);
}

contract Caller {
	 Called public called;
	 constructor (Called addr) public {
		 called = addr;
	}

	function call () {
	  called.fun();
	}
}

ABI not available: delegatecall or call

Calling a function means injecting a specific context (arguments) to a group of commands (function) and commands are executing one by one with this context.

Bytecode

In Ethereum, a function call can be expressed by a 2 parts bytecode as long as 4 + 32 * N bytes.

  • Function Selector: first 4 bytes of function call’s bytecode. Generated by hashing target function’s name plus with the type of its arguments excluding empty space. Ethereum uses keccak-256 hashing function to create function selectors: functionSelectorHash = web3.utils.keccak256('func()')
  • Function Argument: convert each value of arguments into a hex string padded to 32 bytes.

If there is more than one argument, they are concatenated. In Solidity encoding the function selector together with the arguments can be done with globally available encoding/decoding functions: e.g. abi.encodeWithSignature("add(uint256,uint256)", valueForArg1, valueForArg2)

Call: doesn't preserve context.

Can be used to invoke public functions by sending data in a transaction. contractInstance.call(bytes4(keccak256("functionName(inputType)"))

call diagram

DelegateCall: preserves context

contractInstance.delegatecall(bytes4(keccak256("functionName(inputType)"))
Delegate calls preserve current calling contract's context (storage, msg.sender, msg.value). The calling contract using delegate calls allows the called contract to mutate its state.

delegatecall diagram

delegatecall mtating state diagram 2

Hack

  1. Compute the encoded hash that will be used for msg.data
  2. Send msg.data in a transaction to the contract fallback

Takeaways

  • Use the higher level call() function to inherit from libraries, especially when you don’t need to change contract storage and do not care about gas control.
  • When inheriting from a library intending to alter your contract’s storage, make sure to line up your storage slots with the library’s storage slots to avoid unexpected state changes..
  • Authenticate and do conditional checks on functions that invoke delegatecalls.