diff --git a/README.md b/README.md index 93ba18a..e718fe5 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,15 @@ Workshop for "web3" bridge hacking at Hacktivity 2022 #### Introduction - Web3 vs web2 hacking, concepts / workshop topology +- Who interacted with dApps/SCs before? +- Who codes Solidity? +- Who codes Rust? +- Who used a bridge before? +- Who is the cryptographer? #### Environment setup, system requirements - Any browser for Ethereum, Remix +- Python3 - Substrate, Rust nightly #### Scenario 1: Token on two chains, mint using receipt @@ -19,8 +25,9 @@ Workshop for "web3" bridge hacking at Hacktivity 2022 #### Scenario 2: Signature forgery (any chain) - Deploy SC on Ethereum chain - Compile Substrate with EVM -- Deploy SC +- Deploy SC on Substrate chain (so it is different from core) - Test ECDSA signature forgery exploit from one to other +- Test same issue with WASM/ink! ## Resources @@ -32,6 +39,7 @@ https://ethereum.org/en/developers/docs/standards/tokens/erc-20/ https://git.hsbp.org/six/eth_keygen #### Scenario 2 +https://cryptoctf.org/2022/09/11/writeup-of-flag-submission-forgery-by-si/ https://github.com/paritytech/substrate-contracts-node https://docs.substrate.io/quick-start/ https://github.com/substrate-developer-hub/substrate-front-end-template @@ -39,4 +47,18 @@ https://github.com/paritytech/ink https://github.com/paritytech/substrate/blob/master/primitives/core/src/ecdsa.rs https://use.ink/getting-started/setup https://medium.com/block-journal/introducing-substrate-smart-contracts-with-ink-d486289e2b59 +https://github.com/paritytech/contracts-ui +https://contracts-ui.substrate.io/?rpc=wss://rpc.shibuya.astar.network https://substrate.io/developers/playground/ +https://security.stackexchange.com/questions/200682/is-it-possible-to-fake-ecdsa-signatures + + +## Solidity Hacking Homework + +1. Crypto Wojak - Who is the admin? +2. Sminem - Set the password +3. Crypto Wojak - Make your tries count 2 or more +4. Crypto Wojak - Make your tries count 2 and get the answer +5. HODLer - Deposit ether twice with the same address +6. Crypto Wojak - Execute selfdestruct() ++1 Sminem - Create a signature to prove you are Satoshi (see js folder) diff --git a/docs/Workshop_-_Pwn_web3_through_multichain_attacks.pdf b/docs/Workshop_-_Pwn_web3_through_multichain_attacks.pdf new file mode 100644 index 0000000..95eaf8d Binary files /dev/null and b/docs/Workshop_-_Pwn_web3_through_multichain_attacks.pdf differ diff --git a/docs/topology.drawio b/docs/topology.drawio new file mode 100644 index 0000000..8d92750 --- /dev/null +++ b/docs/topology.drawio @@ -0,0 +1 @@ +7VfbcpswEP0aHp3hEhz7Mb6kyTSepnEmaZ4yMsigjkBUCF/69V2BBKiYNO4lk077YrNHq5W0e/YILG+a7N5xlMULFmJquXa4s7yZ5brj0xH8SmBfAb47roCIk7CCnAZYkq9YgbZCCxLi3HAUjFFBMhMMWJriQBgY4pxtTbc1o+aqGYpwB1gGiHbRBxKKuEJHvt3gl5hEsV7ZsdVIgrSzAvIYhWzbgry55U05Y6J6SnZTTGXudF6qeRc9o/XGOE7FSyZc5Veb2SD/8PgxDO5D7C0G2fvBqdqb2OsD4xDOr0zGRcwiliI6b9AJZ0UaYhnVBqvxuWYsA9AB8DMWYq+KiQrBAIpFQtUo3hHxSU4/8ZX12BqZ7VTk0thrIxV8X01yfW0/tgebeaWlJ+YCcXEumQBAQFGek0DDF4TqPVV5kIfvTa+CclbwAD+TU01TxCMsnvHzahJA82CWYNg2zOOYIkE25j6QonFU+6mpcDK0bzlkjKQib0W+kQA4qIZ0NXP3pn3R4++Nvef84aHagbZaR2mgkolHsNJRa24QLVQeLu/ubix3SCGhkxU3KDv8UsgemqxZKgZ5SbpzcHDcbFcWVo/DUyT/H5bLg6y/RivQLoOpiJIolbwBEmAOwAZzQUAdztVAQsKwagoMK6NVGU/STtUBgvsTy5+p7amWcNyacTIg3h2SMhWsEZA2F/ubuUsoFd0+8Ya2YxRz4FbmcZTrcGTgHQ6rI7D1OodOMGnze4jS4UnNEV3tuYgxx0UCbiuUg2xBgmNEUu0I69a+bYL1IfPbqSyIvUxAQOB/CmXlCG6ejucCkvT0QET8dIsDTDLRZR2lcI9J+mxjIvAyQ6WwbOEmNXnYy5WOPvWW3xmajX+mzG1zqzkjhcXtG+3U7ueDUc9ji+f/hXfP2Ru/e9xXunt+qfCO+1/ef0Le/R/Iu+MOR39C3h3ffAN4LXHvsqQj7stilYP6Ctyn7o0ab0GIYXR+v/jXlN/33pryd1/vOpWdwNcg6OGLSlUn2c4428D3In9JOS/hWHA2mQhEqaSwzVLJI1YSpaRRftKpXHMDOa9SPe/7F/bxgfKdHSqfc3z5wGw+SKs+br7qvfk3 \ No newline at end of file diff --git a/docs/topology.png b/docs/topology.png new file mode 100644 index 0000000..b6a7c50 Binary files /dev/null and b/docs/topology.png differ diff --git a/js/i_am_satoshi.js b/js/i_am_satoshi.js new file mode 100644 index 0000000..50c6866 --- /dev/null +++ b/js/i_am_satoshi.js @@ -0,0 +1,72 @@ +'use strict'; + +// original: https://gist.github.com/indutny/8d0f5376ee643962a9f0 + +// npm install --save bn.js +// npm install elliptic +// npm install bcoin +// npm install secp256k1 + +const BN = require('bn.js'); +const elliptic = require('elliptic'); +const bcoin = require('bcoin'); + +const ecdsa = new elliptic.ec('secp256k1'); + +let message = new BN( + '7a05c6145f10101e9d6325494245adf1297d80f8f38d4d576d57cdba220bcb19', 'hex'); + +var key = new Buffer('0411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3', 'hex'); +var sig = '304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d09'; +// const signature = new bcoin.ecdsa.signature(new Buffer(sig, 'hex')); +// console.log(signature); + +var signature = { + r: new BN('4e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd41', 'hex'), + s: new BN('181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d09', 'hex') +}; + +const point = ecdsa.curve.pointFromX(signature.r); +point.precompute(256); + +function trick(message, signature, i) { + const n = new BN( + 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', 16); + const p = new BN( + 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', 16); + + const nRed = BN.red(n); + const pRed = BN.red(p); + + // NOTE: Could be using GLV values for speed + let lambda = new BN(i); + + const point2 = point.mul(lambda); + let beta = point2.x.redMul(point.x.redInvm()).fromRed(); + + lambda = lambda.toRed(nRed); + beta = beta.toRed(pRed); + // NOTE end + + const originalR = signature.r; + const r = originalR.toRed(pRed).redMul(beta).fromRed(); + + const nBeta = r.toRed(nRed).redMul(originalR.toRed(nRed).redInvm()); + const common = lambda.redInvm().redMul(nBeta); + + const s = signature.s.toRed(nRed).redMul(common).fromRed(); + + return { + signature: { r: r, s: s }, + message: message.toRed(nRed).redMul(nBeta).fromRed() + }; +} + +for (let i = 2; i < 100; i++) { + const item = trick(message, signature, i); + console.log(JSON.stringify([ + new Buffer(item.message.toArray()).toString('hex'), + new Buffer(new bcoin.ecdsa.signature(item.signature).toDER()).toString('hex') + ]) + ','); + // ecdsa.verify(item.message, item.signature, key) +} diff --git a/python/bridge.py b/python/bridge.py index b96ee2e..f37d1c0 100644 --- a/python/bridge.py +++ b/python/bridge.py @@ -1,7 +1,6 @@ #!/usr/bin/python3 - # Author: six -# Made for: CCTF during BsidesBUD 2022 +# Made for: CCTF during BsidesBUD 2022 -> Updated for Hacktivity 2022 web3 hacking Workshop # require(owner() == ecrecover(keccak256(abi.encodePacked(this, tokenId)), v, r, s), "Should be signed correctly"); # https://privatekeys.pw/keys/ethereum/1 @@ -10,20 +9,14 @@ from web3.auto import w3 from eth_account.messages import encode_defunct from flask import Flask, request -from base64 import b64encode - app = Flask(__name__) -b64 = '''
Lets play a game... what is the private key if:
-
-SignedMessage(messageHash=HexBytes('0xd65fc3b188dd92cfcb2a193a50840c1b782030fb06c5eee3125dadc48b9042ee'), r=93061353422229139783272046072373682100846510432479107335894930000050943813187, s=18897300799783892124841480819482900155126442998358954848148962214377508383077, v=28, signature=HexBytes('0xcdbedc050cdf4e1a236535365e1563312905fc47aa8238a8ab06decfbb31e64329c77e43945949494800d0c432d037867ea10aad935abf98c63ae2befaf929651c'))
-
-If you send the correct private key as argument to /verify?p= in HEX format (without 0x), then you will get the CCTF flag which lets you in to the next yacht event + swag at BsidesBUD.
+html = '''
Use /get_a_receipt
 
''' @app.route('/') def hello(): - return b64 + return html @app.route('/verify', methods=['GET']) def search(): diff --git a/python/eth_keygen.py b/python/eth_keygen.py index 2858aea..28cb3b7 100644 --- a/python/eth_keygen.py +++ b/python/eth_keygen.py @@ -1,5 +1,5 @@ #!/usr/bin/python3 -# Eth ECDSA key generator, signer and arg recovery for web3 hacking +# Eth ECDSA key generator, signer and arg recovery for web3 hacking // Modified for Hacktivity workshop # Author: six # References: https://web3py.readthedocs.io/en/stable/web3.eth.account.html?highlight=sign#sign-a-message # Ethereum Private Keys Directory -> https://privatekeys.pw/keys/ethereum/1 @@ -84,7 +84,7 @@ parser.add_argument("-s", "--sol", help="Print solidity code for ecrecover", act args = parser.parse_args() if args.dgen: - forv = generate("Anything","0000000000000000000000000000000000000000000000000000000000000001") # hex|0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf + forv = generate("Anything","0000000000000000000000000000000000000000000000000000000000000006") # hex|0xE57bFE9F44b819898F47BF37E5AF72a0783e1141 gen_vrs(forv) sys.exit() if args.rgen: @@ -107,6 +107,6 @@ if args.sol: print_sol_recovery() sys.exit() -forv = generate("Anything","0000000000000000000000000000000000000000000000000000000000000001") # hex|0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf +forv = generate("Anything","0000000000000000000000000000000000000000000000000000000000000006") # hex|0xE57bFE9F44b819898F47BF37E5AF72a0783e1141 gen_vrs(forv) print("\nNo arguments were provided. You can use --help.") diff --git a/python/forgery.py b/python/forgery.py index e858683..db4874b 100644 --- a/python/forgery.py +++ b/python/forgery.py @@ -1,5 +1,8 @@ #!/usr/bin/python3 # Author: SI +# Reference 1: https://cryptoctf.org/2022/09/11/writeup-of-flag-submission-forgery-by-si/ +# Reference 2: https://polygonscan.com/address/0x36a1424da63a50627863d8f65c0669da7347814a +# Reference 3: https://gist.github.com/chjj/4fe8f5b2b489e89e6ed4 from eth_account.account import to_standard_signature_bytes from eth_keys import keys @@ -33,8 +36,10 @@ def forge(public_key, a = 0, b = 1): return '0x' + to_bytes(z).rjust(32, b'\0').hex(), '0x' + eth_signature_bytes.hex() -hsh = '0xe50051a0af89748fe098cef3b163b6dc586a664e726791bb2a582ad364f42683' -sig = '0x2bdbc1826efc039719a28a9f4dbab9f4a2692d83de478300261a0e49019b63ee67c202ecc4ebdf82693da47824ac4fcf21f793400d85696034c4de9537c6ce491b' +#hsh = '0xe50051a0af89748fe098cef3b163b6dc586a664e726791bb2a582ad364f42683' +#sig = '0x2bdbc1826efc039719a28a9f4dbab9f4a2692d83de478300261a0e49019b63ee67c202ecc4ebdf82693da47824ac4fcf21f793400d85696034c4de9537c6ce491b' +hsh = '0xbb272d3dc886fccf69f92cd7cb622501c02627c045fb38053f78af2dca68e188' +sig = '0x30410a2d097af2b27ba3d789ce151c6aed0590f71b4c7b67d4ae91f56659f2297c3a2f8155d9fb9d30682e599b31012b51cb0928578e97f2f3f0c306597d2eec1c' pub = recover_public_key(hsh, sig) addr = pub.to_checksum_address() diff --git a/solidity/multichain_safu.sol b/solidity/multichain_safu.sol index db470b9..ade1280 100644 --- a/solidity/multichain_safu.sol +++ b/solidity/multichain_safu.sol @@ -1,9 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-or-later // Challenges inspired by CCTF pragma solidity ^0.8.17; -import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; -contract SafuDotERC20 is AccessControlUpgradeable { +contract SafuDotERC20 { uint256 public max_supply; mapping (address => uint256) internal amountToAddress; @@ -18,7 +17,7 @@ contract SafuDotERC20 is AccessControlUpgradeable { mapping(address => uint256) public calls; mapping(address => uint256) public tries; - //bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + address public bridge; mapping(address => bool) public minter; // Homework 1 @@ -29,8 +28,10 @@ contract SafuDotERC20 is AccessControlUpgradeable { max_supply = 1000000; amountToAddress[msg.sender] = max_supply - 100000; - amountOfBridge = max_supply - amountToAddress[msg.sender]; + bridge = 0xE57bFE9F44b819898F47BF37E5AF72a0783e1141; + amountToAddress[bridge] = max_supply - amountToAddress[msg.sender]; mint_amount = 10000; + minter[msg.sender] = true; } @@ -38,33 +39,38 @@ contract SafuDotERC20 is AccessControlUpgradeable { amountToAddress[msg.sender] = amountToAddress[msg.sender] + mint_amount; } - function mintWithReceipt( - address recipient, - uint256 amount, - uint256 uuid, - uint8 v, - bytes32 r, - bytes32 s - ) public { - - bytes32 payloadHash = keccak256(abi.encode(recipient, amount, uuid)); - bytes32 hash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", payloadHash)); + function mintWithReceipt(bytes32 _message, bytes memory _signature) public { + require(recoverSigner(_message, _signature) == bridge, "Not signed with the correct key."); + //require(minter[msg.sender] == true, "Not minter"); //require(!_receipts[hash], "Receipt already used"); // Dumb without it. - _checkSignature(hash, v, r, s); + amountToAddress[bridge] = amountToAddress[bridge] - mint_amount; + require(amountToAddress[bridge] >= 0); mint(); //_receipts[hash] = true; } - function _checkSignature( - bytes32 hash, - uint8 v, - bytes32 r, - bytes32 s - ) internal view { - address signer = ecrecover(hash, v, r, s); - require(minter[signer] == true, "Not minter"); + function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature) public pure returns (address) { + (bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature); + return ecrecover(_ethSignedMessageHash, v, r, s); } + function splitSignature(bytes memory sig) public pure returns (bytes32 r, bytes32 s, uint8 v){ + require(sig.length == 65, "Invalid signature length"); + assembly { + r := mload(add(sig, 32)) + s := mload(add(sig, 64)) + v := byte(0, mload(add(sig, 96))) + } + } + + function myBalance() external view returns (uint256) { + return amountToAddress[msg.sender]; + } + + + function othersBalance(address _other) external view returns (uint256) { + return amountToAddress[_other]; + } function transfer(uint256 _value, address _toAddress) external { require(amountToAddress[msg.sender] - _value >= 0, 'Oooops'); @@ -77,7 +83,6 @@ contract SafuDotERC20 is AccessControlUpgradeable { return sent; } - // Easypeasy function adminChange(address _newAdmin) external returns (bool) { admin = _newAdmin; @@ -90,7 +95,7 @@ contract SafuDotERC20 is AccessControlUpgradeable { correct_password = _password; } - // Homework 5 - Final + // Homework 6 - Final function su1c1d3(address payable _addr, string memory _password) external { require(msg.sender == admin, 'You are not the central admin!'); require(keccak256(abi.encodePacked(correct_password)) == keccak256(abi.encodePacked(_password)), 'Very sekur.'); @@ -113,7 +118,12 @@ contract SafuDotERC20 is AccessControlUpgradeable { return answer; } - function deposit() public payable {} - fallback() external payable {} - receive() external payable {} + // Homework 5 + mapping(address => bool) address_contributed; + function deposit() public payable { + require(address_contributed[msg.sender] != true, "Not working."); + address_contributed[msg.sender] = true; + } + fallback() external {} + //receive() external {} }