---
# System prepended metadata

title: 一个通过后门进行的攻击
tags: [Security, Solidity]

---

## 0x00: 背景
[R0ARStaking](https://etherscan.io/address/0xbd2cd71630f2da85399565f6f2b49c9d4ce0e77f#code) 智能合约是一个质押协议，用户可以存入LP Token，以定期获取收益。但令人意想不到的是，合约开发人员在构建该去中心化金库的同时，留下了一个独属于自己的**后门**。

该恶意开发人员使用特定的[账户](https://etherscan.io/address/0x8149f77504007450711023cf0ec11bdd6348401f)，往协议中存入极少的LP Token。而在后续，却能够提取出协议的全部资金。那么，这其中发生了什么？
<br>

## 0x01: 原因定位
从R0ARStaking合约的交易记录上看，该恶意账户进行了三个交易：
![image](https://hackmd.io/_uploads/rk4KV1xkxx.png)

<br>

在最后一步，账户调用了合约的`EmergencyWithdraw()`函数来提取资金。该函数中有两处涉及资金转移，转移数量分别由`rewardAmount`和`withdrawAmount`决定，而这两个数量又是根据`user.amount`计算得来。
![image](https://hackmd.io/_uploads/HJwIQ1xyll.png)

<br>

那`user.amount`中的值是怎么来的？`Harvest()`函数不会改变该变量，那么来看看`Deposit()`:
![image](https://hackmd.io/_uploads/BJlJ9P1gJeg.png)
在`Deposit()`函数中，把传入的`_amount`叠加到`user.amount`。但问题是，该账户调用`Deposit()`时，传入的`_amount`是很小很小的，完全不足以提取出协议的全部资金。

<br>

与此同时，发现了一段奇怪的代码：
![image](https://hackmd.io/_uploads/H1t9yJgylx.png)

为什么说奇怪？这里定义了`NAME_SLOT`和`NAME_HASH`，在把`NAME_HASH`的值存到`NAME_SLOT`所指定的槽位后，这两个变量却没有在其他地方明确使用到。

<br>

其实结合上面`user.amount`的“灵异现象”，可以进行猜测：`NAME_SLOT`所指定的槽位可能是该恶意账户`user.amount`变量的槽位。对该槽位存入`NAME_HASH`, 相当于对`user.amount`赋了一个很大的值。

<br>

## 0x02: 验证想法
为了验证想法，可以计算一下该恶意账户`user.amount`变量所占的槽位是多少。

计算槽位需要对存储布局有一定的了解，如果相关知识欠缺，相信RareSkills的这两篇文章会对你很有帮助：
1. https://www.rareskills.io/post/evm-solidity-storage-layout
2. https://www.rareskills.io/post/solidity-dynamic

<br>

首先，我们来看看R0ARStaking的继承关系，以及合约中包含的变量：
![image](https://hackmd.io/_uploads/rJ_xtxxJxg.png)


恶意账户在调用`EmergencyWithdraw()`时，传入的`_pid`为0，同时知道账户地址为`0x8149f77504007450711023cf0eC11BDd6348401F`。

因此，该恶意账户的`user.amount`，也相当于为：
```
userInfo[0][0x8149f77504007450711023cf0eC11BDd6348401F].amount
```

<br>

具体计算步骤：
1. 获取`userInfos`变量的基础槽位：`userInfo_baseSolt` = 4;
2. 通过第一个key(pid = 0)获取内部的基础槽位：`userInfo_innerBaseSlot = keccak256(key01, userInfo_baseSolt)`;
3. 通过第二个key(恶意账户地址)获取`UserInfo`结构体的槽位：`UserInfo_slot = keccak256(key02, userInfo_innerBaseSlot)`
4. `user.amount`的槽位等于`UserInfo_slot + 1`;

<br>

计算代码：
``` solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract calcSlot {
    function calcslot() pure external returns(bytes32) {
        // struct UserInfo { Deposit[] deposits; uint256 amount; }
        // mapping (uint256 => mapping (address => UserInfo)) public userInfo;
        uint256 userInfo_baseSolt = 4;
        uint256 key01 = 0;   // pid
        bytes32 userInfo_innerBaseSlot = keccak256(abi.encode(key01, userInfo_baseSolt));
        address key02 = 0x8149f77504007450711023cf0eC11BDd6348401F;  // hacker address
        bytes32 UserInfo_slot = keccak256(abi.encode(key02, userInfo_innerBaseSlot));
        bytes32 userAmount_slot = bytes32(uint256(UserInfo_slot) + 1);
        return userAmount_slot;
    }
}
```

<br>

计算结果：
![image](https://hackmd.io/_uploads/SkGhtegJxx.png)

可以看出，得到的槽位和`NAME_SLOT`是一致的：
```
bytes32 private constant NAME_SLOT = 0x7320715e23a405b30bfe5ed8f0781f194fa396c23546626bf8c46dcb790e6597;
```
<br>

## 0x03: 总结
总的来说，就是恶意开发人员在部署质押合约，执行构造函数时，就给自己的账户设置了极大的金额数值。在等到他人存入资金后，可以随时使用该账户收一波网。因为设置的数值极大，足以把协议的资金全部取出来。

<br>