使用Slither智能合约存储分析工具深入挖掘区块链数据

本文详细介绍了slither-read-storage工具如何通过静态分析技术解析Solidity智能合约的存储布局,包括动态数组长度获取、存储槽计算和实际应用场景,帮助开发者和安全审计人员高效处理区块链数据。

使用Slither智能合约存储分析工具

您认为在Solidity智能合约中发现了一个关键漏洞,如果被利用,可能会耗尽广泛使用的加密货币交易所的资金。为了确认这确实是一个漏洞,您需要找出一个没有getter方法的模糊存储槽中的值。肾上腺素在您的体内流动,但当您看到Solidity存储文档时, devastation的刺痛随之而来:

您的动力崩溃了,因为您试图解释这些象形文字,知道每一秒的浪费都可能是灾难性的。幸运的是,slither-read-storage,一个从源代码检索存储槽的工具,可以将您从这个噩梦中拯救出来。

什么是slither-read-storage?

slither-read-storage通过使用Slither底层类型分析的信息(例如,数组是固定还是动态)来通知槽计算,从源代码检索存储槽。

该工具可以检索单个变量或整个合约的存储槽,并且可以通过提供以太坊RPC来检索存储值。

slither-read-storage包含在Slither的最新版本0.8.3中,可以使用Python的包管理器pip安装:

1
pip install slither-analyzer==0.8.3

slither-read-storage的用例

让我们探索一些该工具的用例。

月光审计

要确定所有可以铸造FRAX的地址,我们必须手动在Etherscan上逐个输入frax_pools_array的索引。由于frax_pools_array是动态的,了解数组的当前长度会很有帮助,但这并不容易获得。以这种方式查找这些地址非常耗时,我们可能会浪费时间输入越界的索引。

在Etherscan上查询frax_pools_array

相反,我们可以在FRAXStablecoin合约的地址上运行slither-read-storage来找到frax_pools_array的长度:

1
slither-read-storage 0x853d955aCEf822Db058eb8505911ED77F175b99e --variable-name frax_pools_array --rpc-url $RPC_URL --value

太好了!动态数组的长度存储在合约的一个槽中——在这种情况下是槽18。检查FRAXStablecoin的槽18的值,我们可以看到数组的长度是25。

1
2
3
4
5
6
7
8
9
INFO:Slither-read-storage:
Contract 'FRAXStablecoin'
FRAXStablecoin.frax_pools_array with type address[] is located at slot: 18
INFO:Slither-read-storage:
Name: frax_pools_array
Type: address[]
Slot: 18
INFO:Slither-read-storage:
Value: 25

现在,我们可以检索整个FRAXStablecoin存储,并指定–max-depth 25标志来传递我们希望slither-read-storage返回的数据结构的最大深度:

1
slither-read-storage 0x853d955aCEf822Db058eb8505911ED77F175b99e --layout  --rpc-url $RPC_URL --value --max-depth 25

该工具将生成一个包含存储布局和值的JSON文件,但我们只对frax_pools_array感兴趣。截至撰写本文时,该工具检索了25个元素,指示可以铸造FRAX的地址。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
"frax_pools_array": {
"type_string": "address[]",
"slot": 18,
"size": 256,
"offset": 0,
“value”: 25,
"elems": {
// 省略
"23": {
"type_string": "address",
"slot": 84827061063453624289975705683721713058963870421084015214609271099009937454171,
"size": 160,
"offset": 0,
"value": "0x36a0B6a5F7b318A2B4Af75FFFb1b51a5C78dEB8C"
},
"24": {
"type_string": "address",
"slot": 84827061063453624289975705683721713058963870421084015214609271099009937454172,
"size": 160,
"offset": 0,
"value": "0xcf37B62109b537fa0Cb9A90Af4CA72f6fb85E241"
}
}

套利机器人

除了在Etherscan上进行月光审计外,slither-read-storage还可以用于提高程序的速度。例如,我们可以使用slither-read-storage让程序直接访问以太坊节点的数据库,而不是处理RPC调用。这对于不提供视图函数来检索所需变量的合约尤其有用。

例如,假设一个套利机器人频繁读取WETH/USDC Uniswap V3池上变量slot0的成员sqrtPriceX96(参见下面的数据结构)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
struct Slot0 {
// 当前价格
uint160 sqrtPriceX96;
// 当前tick
int24 tick;
// 观察数组最近更新的索引
uint16 observationIndex;
// 当前存储的观察的最大数量
uint16 observationCardinality;
// 下一个要存储的观察的最大数量,在observations.write中触发
uint16 observationCardinalityNext;
// 当前协议费用作为提款时交换费用的百分比
// 表示为整数分母(1/x)%
uint8 feeProtocol;
// 池是否锁定
bool unlocked;
}

我们可以使用slither-read-storage来计算槽,而不是调用提供的视图函数,如下所示:

1
slither-read-storage 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 --layout

该工具生成的文件包含sqrtPriceX96的槽、大小和偏移量,可以轻松地从以太坊节点的键值存储中检索,并根据大小和偏移量进行切片。事实证明,Uniswap开发者恰当地命名了这个变量slot0,但这在实践中很少可用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
"slot0": {
"type_string": "UniswapV3Pool.Slot0",
"slot": 0,
"size": 256,
"offset": 0,
"elems": {
"sqrtPriceX96": {
"type_string": "uint160",
"slot": 0,
"size": 160,
"offset": 0
},
/// 省略

此外,可以使用以太坊节点RPC eth_call通过传入存储槽和所需值来修改存储值,并模拟应用于修改状态的交易如何受到影响。有关如何完成此操作的更多详细信息,请参阅本教程。

投资组合跟踪

可以使用以下slither-read-storage命令找到此账户的余额槽,其中令牌地址作为目标,账户地址作为键:

1
slither-read-storage 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984 --variable-name balances --key 0xab5801a7d398351b8be11c439e05c5b3259aec9b --rpc-url $RPC_URL --value

在这个特定实例中,槽是我们打算检索的账户地址0xab5801a7d398351b8be11c439e05c5b3259aec9b,以及槽9,填充到32字节:

1
000000000000000000000000ab5801a7d398351b8be11c439e05c5b3259aec9b0000000000000000000000000000000000000000000000000000000000000009

该值使用keccak256进行哈希,余额使用SSTORE(slot, value)写入结果槽。

可升级的ERC20令牌

正常的智能合约在同一地址具有合约逻辑和存储。然而,当使用delegatecall代理模式(允许合约可升级)时,代理调用实现,存储值写入代理合约。也就是说,DELEGATECALL操作码使用逻辑合约的存储信息写入调用者地址的存储。为了适应这种模式,需要–storage-address标志来检索相同地址的余额槽:

1
slither-read-storage 0xa2327a938Febf5FEC13baCFb16Ae10EcBc4cbDCF --variable-name balances --key 0xab5801a7d398351b8be11c439e05c5b3259aec9b --storage-address 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 --rpc-url $RPC_URL --value

结束语

通过构建这个工具,我学到了很多关于Solidity的存储处理和Slither的API,它的发布完成了我作为学徒加入Trail of Bits之前开始的工作。事实上,我可以将我在这里的宝贵经验归功于在Twitter上讨论Slither并在开发此工具的第一次迭代时发现Slither中的问题。

如果您热衷于做同样的事情,请查看我们的GitHub并获取一个开放问题。我们很乐意帮助新的贡献者。

如果您喜欢这篇文章,请分享: Twitter LinkedIn GitHub Mastodon Hacker News

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计