delegatecall(SafaDelegatecall)
contract
1 | pragma solidity ^0.4.23; |
analyses
code
Our goal is to trigger the event SendFlag()
, but only owner could call it and no code can set owner. Let’s look at execute()
, maybe it can do something.
1 | function execute(address _target) public payable{ |
this is execute()
logic:
(1) call _tartget’s execute()
, and it should be execute wrong and return false
(2) getRet()
should return 2 values, one is bytes4(keccak256('fifth(uint256)'))
and the other one “val” is anything u like. But “val” should be a specific value or you can not complete this level.
(3) func.f = gift
and func.f()
: it will call gift()
1 | function gift() private { |
(4) assembly
mload(func)
: get the address offunc
in memory ==> get the address ofgift
in memory, because thestruct
‘s feature. If you know the EVM storage ofstruct
you can understand it.sub(mload(func), val)
: address(gift) - val, and the val is decided by us. So we can create a target contract, and the program execution flow can go anywhere.mstore(func, sub(mload(func), val))
: placesub(mload(func), val)
in the 32 bytes after addressfunc
. This means that if we callfunc.f()
, it would jump to the addressfunc
and execute the code after the addressfunc
. We want to jump toemit SendFlag(msg.sender);
, we need to find its address and then we can jump to this code directly without passing therequire()
andonlyOwner
1 | function payforflag() public payable onlyOwner { |
So we should find the “val”. And we should analyses getRet()
:
1 | function getRet() internal pure returns (bytes4 sel, uint val) { |
returndatasize
: size of the last returndata, in our level it is the value that delegatecall returnseq(returndatasize, 0x24)
: ifreturndatasize
is 0x24 bytes?iszero(eq(returndatasize, 0x24))
: 0x24 bytes==>true==>1==> not revert(), or it will revert()let ptr := mload(0x40)
: get the free memory pointerreturndatacopy(ptr, 0, 0x24)
: copy s bytes from returndata at position f to mem at position t. In our level, it is put the value that delegatecall returned in the address of ptr(0x40).mload(ptr)
: get 32 bytes after address ptrsel := and(mload(ptr), 0xffffffff00000000000000000000000000000000000000000000000000000000)
: get the first 4 bytes ==>bytes4(keccak256('fifth(uint256)'))
add(0x04, ptr)
: pass 4bytes==>pass address(sel)val := mload(add(0x04, ptr))
: get the following 32 bytes==>val, anything we can decide
search for the “val”
address(emit …) = address(gift) - val, equal to val = address(gift) - address(emit …), We need to decompile the contract to find the address.
The title address is in goerli: 0x43E9663D23bBafc76630f7c933A028dE85892E40
, and I decompile it in this site.
To find emit SendFlag(msg.sender);
, we can search for opcodeSELFDESTRUCT
.
From this picture, we can infer it contains emit SendFlag(msg.sender)
because of LOG1
but not contains require()
because of no *REVERT
, so address 03C1
is the address(emit ...)
Also, you can find emit SendFlag(msg.sender)
throught require(msg.value == 1,'I only need a little money!')
since it only appears once in the contract. From this picture, it approves 03c1
is right. Jump to emit SendFlag(msg.sender)
that makes us escape from the require()
check.
To find address(gift)
, we can search with *REVERT
and opcode MSTORE
. And now we know 048a
is address(gift)
Now we know that: val = address(gift) - address(emit …) = 048A - 03C1 = 1162 - 961 = 201 = C9
This is a picture created by me, I hope it is helpful for you to understand this level:
solve
1 | // SPDX-License-Identifier: UNLICENSED |
From this level, I know that if we have inline assembly, we can jump to anywhere we like even pass important check!
Let’s solve it!
destruct contract successfully
trigger successfully :)