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 @@
+
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 {} }