题目预览
题目代码:
pragma solidity ^0.4.21;
contract 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);
}
}
目标依然是调用isComplete函数,此题中就是需要题目合约的余额小于1ether。
由于属于math系列,所以我看的时候对运算很敏锐,很容易就能发现require(msg.value == numTokens * PRICE_PER_TOKEN);会存在一个很明显的溢出,只要我们numTokens传的足够大,在乘以10**18次方后,我们的msg.value就能够超过这个溢出之后的值,我们的余额就会变得非常巨大。
攻击合约:
contract attack{
uint256 max = 2**256-1;
uint256 public num1;
uint256 public num2;
function att()public{
num1 = max/10**18;
num2 = (num1+1)*10**18;
}
}
攻击合约没啥目的,只是为了算出多大的数可以溢出,以及我们需要付出多少的value。

我们在buy中传入num1+1,并给出比num2相等或更多的wei,就能够让我们的余额发生溢出。

如图所示,余额很大,所以我们提出1ether毫无问题,合约剩下的eth不足1eth,isComplete成功调用,完成攻击。

TokenWhale合约:
pragma solidity ^0.4.21;
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);
}
}
完成此关,需要我们的token余额大于1000000.
通读合约,合约中有两种转账方式,一种transfer,一种transferFrom,transfer中我并没有看出很明显的漏洞。
但我注意到tansferFrom调用的内部转账函数也是_transfer,而在transferFrom中require(balanceOf[from] >= value);限制的是from的余额大于value,但_transfer中减少的是调用者的余额,所以只要from和调用者不是同一个地址,且to和调用者也不是一个地址,那么调用者的余额就会发生下溢。
首先部署时输入我们第一个账户的地址。

对第二个账户进行approve

使用第二个账户进行transferFrom

调用完成后第二个账户的balance就发生了下溢,我们再把余额转给第一个账户即可。


完成攻击。

RetirementFund合约:
pragma solidity ^0.4.21;
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);
}
}
此关要求我们让合约的余额为零。
这个合约有两种方式可以取走合约所有的钱,一个是withdraw函数,owner在十年后可以取走,但合约中好像没有方法使我们成为owner。
第二种方式是collectPenalty函数,我们可以直接调用uint256 withdrawn = startBalance - address(this).balance require(withdrawn > 0);;但需要我们饶过这个限制,初步看起来好像没有办法,因为合约不存在payable函数,也就无法向其中存入余额。
但自毁合约是可以无条件向任意地址转账的,只要我们通过自毁合约向目标合约转账,就可以让withdraw发生下溢,绕过限制。
攻击合约:
contract attack{
function destruct(address _addr)public payable{
selfdestruct(_addr);
}
}
合约就一个目的,通过自毁向目标合约转钱。
首先部署目标合约并传入我们的账户地址。

然后部署攻击合约,调用att函数并将目标合约的地址放入。

调用collectPenalty函数即可提出eth并完成攻击。

题目合约:
pragma solidity ^0.4.21;
contract 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];
}
}
这个题要求我们将isComplete变为true。
原理很简单,除了isComplete就只剩下一个map数组了,而这个数组的长度和值我们也可以任意设定,所以只要让数组的长度变得很大,发生溢出即可。
攻击合约:
contract figure{
function fig()public pure returns(uint256){
return (uint256(-1)-uint256(keccak256(bytes32(1))))+1;
}
}
攻击合约只是用来计算从map的起始位置到他发生溢出需要多少位即可,得到的结果是35707666377435648211887908874984608119992236509074197713628505308453184860938。
原理很简单,因为在solidity中,在storage中会初始化数组的长度,而数组的起始位置是通过keccak256(bytes32("数组长度所在插槽"))计算的,所以用2**256-1再减去这个数加一就可以让数组的最后一位是整个storage的第一位,覆盖isCompleted变量。
调用set函数将value设定为1,isComplete即可变为true。
题目合约:
pragma solidity ^0.4.21;
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);
}
}
题目要求我们取走合约中剩下的所有钱。
很明显能发现我们需要成为owner才能够将钱提取出来,但合约中似乎没有能够直接改变owner的值,但我们注意到他定义了一个结构体和一个结构体切片。

并且在donation函数中直接初始化了结构体再将其存入数组,这样的操作导致的直接后果就是slot0的数组长度和slot1的owner会分别被timestamp和etherAmount所覆盖,所以我们可以通过数组etherAmount的方式,直接覆盖结构体。
攻击合约:
contract figure{
function fig(uint256 amount)public pure returns(uint256){
return amount/10**36;
}
}
攻击合约的目的只是为了计算出我们需要给donation传入的eth,从而绕过require(msg.value == etherAmount / scale);限制。

传入我们的地址,输入对应的wei值,再调用donate函数,owner就变成了我们的地址

withdraw后,攻击完成。
题目合约:
pragma solidity ^0.4.21;
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);
}
}
合约要求我们取走合约中所有的余额。
我们很容易注意到合约中存在结构体和关于它的数组,也就能够联想到变量覆盖。
整个合约简单来说,就是玩家向合约中存钱,在经过一段时间后才能够将钱提出来,构造函数中将我们传入的一ether存入了数组,并且五十年后才能取出,且在之后的每次存钱,都会在五十年的基础上增加一天。
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);
}
if代码块本身的逻辑是没有问题的,并不会出现变量覆盖的情况,但else就不一样了,由于没有进if,相当于直接创建了一个结构体变量,这势必会造成变量覆盖,而能够覆盖的就是数组的长度以及head。而在取钱的时候,head必须为零我们才能取出数组中第一个元素的余额,也就是最开始的1 ether。
并且,require(timestamp >= queue[queue.length - 1].unlockTimestamp + 1 days);在这个require中很明显存在一个溢出的问题,只要数组最后一个元素的unlockTimestamp够大,这里就会出现溢出。
攻击逻辑已经出来了,我们来分析一下具体步骤:
首先我们要让我们第一次传入的unlockTimestamp足够大,才能够在第二次传入的时候发生溢出,才能够让head为0。
我们计算出需要传入的unlockTimestamp为115792089237316195423570985008687907853269984665640564039457584007913129553536。
由于数组的长度取决于我们传入的wei数,而在push中会对数组的length++,length又跟amount公用一个slot,所以我们传入1wei则长度和amount都2,我们两次都传入1wei,则第一次push的结构体会被第二次push的结构体覆盖,具体原因我就不一一阐释了。
而第二次的timeStamp为0,我们可以很轻松通过withdraw的require,分析到这里,已经可以开始攻击了。
第一步:调用upsert方法,传入index(只要大于1就好),timestamp为上述溢出值

第二步,调用upsert方法,传入index(大于2就好),timestamp为0

第三步:调用withdraw方法传入1即可,到这里攻击完成

这真的很奇怪::josh@josh;wgetftp://ftp.ruby-lang.org/pub/ruby/1.8/ruby-1.8.7.tar.bz2:josh@josh;tarxvjfruby-1.8.7.tar.bz2:josh@josh;cdruby-1.8.7/:josh@josh;CFLAGS='-O0-g-Wall'./configure--disable-pthread:josh@josh;makegcc-O0-g-Wall-DRUBY_EXPORT-D_GNU_SOURCE=1-I.-I.-carray.c[...]gcc-O0-g-Wall-DRUBY_EXPOR
Math中的方法可以像类方法一样调用:Math.cos(0)但也可以是include-d像实例方法:includeMathcos(0)相比之下,以下模块只能以一种方式调用,而不能以另一种方式调用:moduleFoodefbarendendFoo.bar()#NoMethodErrorforthiscallincludeFoobar()#butthiscallisfine单例方法:moduleFoodefself.barendendFoo.bar()#thiscallisfineincludeFoobar()#butnotthisone知道如何编写像Math这样的模块吗?
我想知道这是不是真的:当我对一个平方整数求平方根时,就像在f=Math.sqrt(123*123)我将得到一个非常接近123的float。由于浮点表示精度,这可能类似于122.99999999999999999999或123.000000000000000000001。因为floor(122.999999999999999999)是122,我应该得到122而不是123。所以我希望floor(sqrt(i*i))==i-1在大约50%的情况下。奇怪的是,对于我测试过的所有数字,floor(sqrt(i*i)==i。这是一个用于测试前1亿个数字的小ruby脚本:100_000_000.
这是一道简单题题目来自:https://leetcode.cn/problems/two-sum/题目给定一个整数数组nums和一个整数目标值target,请你在该数组中找出和为目标值target的那两个整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。你可以按任意顺序返回答案。提示:22nums.length104−109−109nums[i]109−109−109target109只会存在一个有效答案进阶:你可以想出一个时间复杂度小于O(n2)O(n^2)O(n2)的算法吗?示例1:输入:nums=[2,7,11,15],targe
我想知道如何在Ruby中获得幂的倒数?2**4#=>16然后我想得到它的逆,但我不确定要使用哪个运算符16??2#=>4 最佳答案 指数的倒数是对数。如果ab=c,然后logac=b.您可以在Math中找到对数函数模块,特别是log()对于base-e和log10()以10为基数。要获得以不同为底的对数(例如n),请使用公式logNa=logxa/logxN,其中x是一个值,例如e或10。针对您的特定案例:log216=loge16/loge2=Math.log(16)/Math.log(2)=4你认为这个解释是好的是因为它扩展了你
我正在使用MathJax来呈现一些数学。我如何摆脱左下角的这条消息?我在MathJax的docs中找不到这个. 最佳答案 这是一个状态栏。每MathJax-Docs,您可以在加载mathjax之前通过将消息样式设置为none来关闭它:MathJax.Hub.Config({messageStyle:"none"}); 关于javascript-MathJax:如何删除"Typesettingmath:100%"显示消息,我们在StackOverflow上找到一个类似的问题:
如果你让我获取数组的最大值,我会这样做:varnums=[66,3,8,213,965,1,453];Math.max.apply(Math,nums);当然,我也可以这样做:nums.sort(function(a,b){returna-b}.pop(nums.length);但我必须诚实。我需要知道为什么有效-使用.apply(Math,nums)。如果我这样做:Math.max(nums);那是行不通的。通过使用apply,我传入Math作为this-以及数组的nums。但我想知道前者有效而后者无效的“为什么”的复杂性。发生了什么魔法?有一些基本的东西我没有全神贯注。我已经阅读了
刚刚在Javascript中尝试在for循环的条件(这就是所谓的?)中生成随机数时遇到了一些有趣的事情。所以,如果我要编写这样的代码:for(vari=0;i它会返回这样的结果:958332684456335345311但是如果我要在第二个for循环之前在变量中生成一个随机数:for(vari=0;i它会返回这样的结果:11131421919171921851518211916151320这里究竟发生了什么?这让我困惑了一会儿。for循环中的Math.random()是否在每次迭代后生成一个新的随机数?循环是否运行代码、迭代和检查条件,并在每次检查条件时生成一个新的随机数?这就是正在发
在IEEE754-2008节"9.2.1Specialvalues"有提到pow(+1,y)is1foranyy(evenaquietNaN)如果没有阅读整个文档,维基百科给出了shortcut:The2008versionoftheIEEE754standardsaysthatpow(1,qNaN)andpow(qNaN,0)shouldbothreturn1sincetheyreturn1whateverelseisusedinsteadofquietNaN.为什么Math.pow(1,NaN)在JavaScript中是NaN?不符合标准吗? 最佳答案
如果轮盘游戏网站csgopolygon.com正在调用Math.random和Math.floor,我如何预测它的结果? 最佳答案 您认为在理论上可以预测Math.random结果的直觉是正确的。这就是为什么,如果你想构建一个游戏/赌博应用程序,你应该确保使用cryptographicallysecurepseudo-randomnumbergenerator.如果他们正在使用这样的东西,那就别管它了。但是,如果您是正确的,并且他们使用System.time作为Java附带的标准随机数生成器的种子,那么可能有办法。这将涉及生成数百万