Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

23 - Code but no code

Description

Level: Leet
Author: HaCk0

Santa loves puzzles and hopes you do too ;) Find the secret inputs which fulfil the requirements and gives you the flag.

Solution

For this challenge we were given two smart contracts. The important part is the following:

contract Challenge {
    bool public solved = false;
    address public signer;

    function solve(address helper, bytes memory sig, bytes calldata message) external {
        for (uint256 i = 0; i < 19; i++) {
            require(bytes20(helper)[i] == 0, "helper has not enought 0s");
        }

        bytes32 r;
        bytes32 s;
        uint8 v = 28;
        assembly {
            // first 32 bytes, after the length prefix
            r := mload(add(sig, 32))
            // second 32 bytes
            s := mload(add(sig, 64))
        }

        (bool success, bytes memory result) = helper.call(
            abi.encode(keccak256(message), v, r, s)
        );
        require(success, "helper call failed");
        require(bytes32(result) == bytes32(uint256(uint160(signer))), "Wrong Signer!");
        solved = true;
    }
}

We have to call the function solve() with the correct parameters to set the sovled flag.

The first check

First, the contract checks that the supplied parameter helper starts with 19 zeros. Clearly, we cannot deploy our own smart contract with such an address since having 19 out of 20 bytes set to zero would take forever. It turn out, however, that there exists a list of so-called precompiled contracts. These always exist and are implemented in the platform itself (they are also not implemented as "normal" contracts). Interestingly enough, these precompiled contracts do start with 19 zero bytes. One of them even has the correct signature, it's the contract 0x01 called ecrecover(hash, v, r, s). This contract recovers the public key based on a signature (v, r, s) and it's corresponding message hash hash.

The second check

The second check takes our supplied message and signature (only consisting of r and s) and checks if the recovered public key is the same as the one of Santa. At this point I got stuck for a while and read up on ECDSA.

The ECDSA produces a signature pair (r, s) during signing but (r, -s mod n) is also a valid signature. The implementation used by Etherium, stores the information whether it's the former or the latter in the value v. v is either 27 or 28 depending on which one should be used (In theory it's possible to have four public keys corresponding to a signature but we will ignore this case for now).

On the website we can let Santa sign an arbitary message for us. However, he always signs it with v = 27 and we need a message where v = 28. To work around this issue we can use the signature malleability described above. We take the signature with v = 27 and swap the v value whilst also adjusting the s value:

# Generated by website
msg = "test"
msg = "\x19Ethereum Signed Message:\n" + str(len(message)) + message
sig = "66c164bfa76d8229cef60ed10251e9aa6fdcfb3510cb6ce73622b28cd3c3a3cd0a6d7118b1b4a0111509d5938154d3f5cae4d1b84b4628031d197e8b772ff60f1b"

r = sig[:64]
s = sig[64:-2]

s1 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - int(s, 16)
newsig = r + hex(s1)[2:]

print("0x" + newsig)
print("0x" + "test".encode("utf-8").hex())

Note that Santa does not sign our message directly (because otherwise it would be possible to sign transactions in his name). Instead, he prepends a special message to it before signing it. This behaviour is described here.

If we now supply all these parameters, we obtain the flag: HV22{H1dd3N_1n_V4n1Ty}.

Unintended solution

There was one unintended solution which was found by LogicalOverflow. Instead of using the signature malleability property, one could simply take the signature of the transaction that deployed the challenge smart contract. That one is signed by the correct public key and uses a v value of 28 (this might just be chance).