その他の関連コンテンツは次の場所にあります:個人ホームページ
EGD 攻撃関連情報
BSCで起こる
-
攻撃者のアドレス:アドレス 0xee0221d76504aec40f63ad7e36855eebf5ea5edd | BscScan
-
攻撃契約:契約アドレス 0xc30808d9373093fbfcec9e026457c6a9dab706a7 | BscScan
-
攻撃トランザクション: BNB スマート チェーン トランザクション ハッシュ (Txhash) の詳細 | BscScan
-
被害者契約のアドレス: EGD_Finance | Address 0x93c175439726797dcee24d08e4ac9164e88e7aee | BscScan
EGD-Financeのコード解析と攻撃プロセスの説明
EGD_Finance コントラクトへの呼び出しはすべて、プロキシ コントラクトを通じて行われます。
ブロックチェーンブラウザで「Read as Proxy」をクリックすると、最終的な契約書が表示されます。
EGD Finance
契約に実装されている主な機能は、USDTを一定期間担保することであり、銀行預金に相当する報酬EGDトークンを出金することができ、一定期間入金した後、利子を引き出すことができます。
以下のステーキング手順と償還報酬手順はすべて、攻撃者によって実際に開始されたトランザクション手順です。
誓約ステップ
アドレス 0xbc5e8602c4fba28d0efdbf3c6a52be455d9558f5 | BscScan は、攻撃コントラクトの関数を呼び出しstake()
、対応する住宅ローン操作を実行します。このアドレスは、攻撃コントラクトを作成した攻撃者のアドレスでもあります。
特定のトランザクションは次のとおりです: BNB スマート チェーン トランザクション ハッシュ (Txhash) の詳細 | BscScan
Phalcon でこのトランザクションの特定の呼び出し情報を確認できます。
さらに詳しい分析は次のとおりです。
EGD_Finance | アドレス 0x93c175439726797dcee24d08e4ac9164e88e7aee | BscScanの関数bond()
は、次の招待者を入力するだけです。web2 と同じである必要があります。各アドレスの招待者は、質権収入に関連しています。
function bond(address invitor) external {
require(userInfo[msg.sender].invitor == address(0), 'have invitor');
require(userInfo[invitor].invitor != address(0) || invitor == fund, 'wrong invitor');
userInfo[msg.sender].invitor = invitor;
userInfo[invitor].refer ++;
}
次のswapETHForExactTokens()
呼び出しは、Defi で非常に一般的なトークン交換操作であり、アドレスからソース コードを見ると、uniswap_v2 の対応する関数と一致しています。
名前から、不確実な量のETHが一定数のトークンと交換されることがわかり、その交換がUSDTであることがわかります。
ユニスワップパラメータリスト
function swapETHForExactTokens(
uint amountOut, // 交易获得的代币数量
address[] calldata path, // 交易路径列表
address to, // 交易获得的 token 发送到的地址
uint deadline // 过期时间
) external virtual override payable ensure(deadline) returns (
uint[] memory amounts // 交易期望数量列表
){
...
}
PancakeSwap: ルーター v2 | アドレス0x10ed43c718714eb63d5aa57b78b54704e256024e | Bscスキャン
function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
external
virtual
override
payable
ensure(deadline)
returns (uint[] memory amounts)
{
//检查是否为WETH进行交换
require(path[0] == WETH, 'PancakeRouter: INVALID_PATH');
// 从library中获知得到amountOut数量的USDT,需要多少ETH
amounts = PancakeLibrary.getAmountsIn(factory, amountOut, path);
//发给pancake的ETH必须大于所需数量
require(amounts[0] <= msg.value, 'PancakeRouter: EXCESSIVE_INPUT_AMOUNT');
// 将 WETH 换成 ETH(对应phalcon的操作)
IWETH(WETH).deposit{value: amounts[0]}();
// 将 amounts[0] 数量的 path[0] 代币从用户账户中转移到 path[0], path[1] 的流动池
assert(IWETH(WETH).transfer(PancakeLibrary.pairFor(factory, path[0], path[1]), amounts[0]));
// 按 path 列表执行交易集合,不细究了,之后再详细看uniswap-qwq
_swap(amounts, path, to);
// 返回多余的ETH
if (msg.value > amounts[0]) TransferHelper.safeTransferETH(msg.sender, msg.value - amounts[0]);
}
最後に、stake()
エージェント契約の関数が呼び出され、100 USDT が誓約されます。Phalcon の通話記録から、特定の関数コードは分析されないことがわかります。主に、その後の報酬計算に必要な多くの関連情報が記録されます。EGD_Finance | Address 0x93c175439726797dcee24d08e4ac9164e88e7aee | BscScan であることがわかります。
特典を引き換える手順
アドレス 0xee0221d76504aec40f63ad7e36855eebf5ea5edd | BscScan攻撃者は、harvest()
攻撃コントラクトの関数を呼び出して、対応する報酬を引き換えます halcon のトランザクション分析は、次の図に示されています。
まず、コントラクトを呼び出して、calculateAll()函数
ユーザーの誓約報酬とユーザーが得られる合計収入を計算します。
function calculateReward(address addr, uint slot) public view returns (uint){
UserSlot memory info = userSlot[addr][slot];
if (info.leftQuota == 0) {
return 0;
}
uint totalRew = (block.timestamp - info.claimTime) * info.rates;
if (totalRew >= info.leftQuota) {
totalRew = info.leftQuota;
}
return totalRew;
}
その後、ユーザーはフラッシュ ローン操作を開始しました。
まず、 (0x16b9a82891338f9ba80e2d6970fdda79d1eb0dae) コールを通じて200 USDTPancakeSwap
が借りられ、その後、楽観的転送とコールバック攻撃コントラクトが前のフラッシュ ローン分析記事で確認できました。swap()函数
pancakeCall()函数
pancakecall()函数中
別のフラッシュ ローンが開始された後、 424456 USDT がPancake LPs
(0xa361433e409adac1f87cdf133127585f8a93c67d)から借用され、再度コールバックされました。これは、ユーザーが引き換える前に約束した報酬です。swap()函数
pancakeCall()函数
claimAllReward()函数
なぜここで報酬が償還されるのかは明らかですが、多額のフラッシュローンが報酬の計算方法に影響を与えるためであり、誓約報酬を計算する関数に抜け穴があるはずです。
次に、PancakeSwap: Router v2 | Address 0x10ed43c718714eb63d5aa57b78b54704e256024e | BscScanプロジェクトに移動してclaimAllReward()
、特定のソース コードを確認し、詳細なコメントを作成します。
function claimAllReward() external {
//判断是否存在对应的质押
require(userInfo[msg.sender].userStakeList.length > 0, 'no stake');
require(!black[msg.sender],'black');
//获取质押时的,一系列质押记录,包括金额、时间戳等等
uint[] storage list = userInfo[msg.sender].userStakeList;
uint rew;
uint outAmount;
uint range = list.length;
//计算对应的奖励
for (uint i = 0; i < range; i++) {
UserSlot storage info = userSlot[msg.sender][list[i - outAmount]];
require(info.totalQuota != 0, 'wrong index');
//不能超过一个最大奖励
uint quota = (block.timestamp - info.claimTime) * info.rates;
if (quota >= info.leftQuota) {
quota = info.leftQuota;
}
//关键步骤,计算对应的奖励,仔细看一下getEGDPrice()函数
//根据EGD的价格,来确定奖励多少EGD
rew += quota * 1e18 / getEGDPrice();
//下面是一些计算账户剩下最大奖励,以及账户余额(+利息)等操作
info.claimTime = block.timestamp;
info.leftQuota -= quota;
info.claimedQuota += quota;
if (info.leftQuota == 0) {
userInfo[msg.sender].totalAmount -= info.totalQuota;
delete userSlot[msg.sender][list[i - outAmount]];
list[i - outAmount] = list[list.length - 1];
list.pop();
outAmount ++;
}
}
//更新相应的质押列表
userInfo[msg.sender].userStakeList = list;
//发送响应的奖励
EGD.transfer(msg.sender, rew);
userInfo[msg.sender].totalClaimed += rew;
emit Claim(msg.sender,rew);
}
function getEGDPrice() public view returns (uint){
//可在phalcon上看到行营的记录
uint balance1 = EGD.balanceOf(pair);
uint balance2 = U.balanceOf(pair);
//EGD的价格仅仅是根据两种代币的实时数量(流动性)来进行计算,可以被攻击者操纵
return (balance2 * 1e18 / balance1);
}
function initialize() public initializer {
__Context_init_unchained();
__Ownable_init_unchained();
rate = [200, 180, 160, 140];
startTime = block.timestamp;
referRate = [6, 3, 1, 1, 1, 1, 1, 1, 2, 3];
rateList = [547,493,438,383];
dailyStakeLimit = 1000000 ether;
wallet = 0xC8D45fF624F698FA4E745F02518f451ec4549AE8;
fund = 0x9Ce3Aded1422A8c507DC64Ce1a0C759cf7A4289F;
EGD = IERC20(0x202b233735bF743FA31abb8f71e641970161bF98);
U = IERC20(0x55d398326f99059fF775485246999027B3197955);
router = IPancakeRouter02(0x10ED43C718714eb63d5aA57B78B54704E256024E);
pair = IPancakeFactory(router.factory()).getPair(address(EGD),address(U));
}
EGD の価格は、1 つのアドレス上の 2 つのトークンの数に基づいて計算されますinitialize()函数
。 でペアのアドレスを取得します。 ペアのアドレスは、ルーターのアドレスに基づいて計算されます。 ルーターはプロキシ契約です。 ブロックチェーン ブラウザー上で、ペアのアドレスが 0xa361433e409adac1f87cdf133127585f8a93c67d であることは、パンケーキに流動性を提供するコントラクトであるため、見覚えがあるように思えます。
この時点で、攻撃が成功した理由がわかったはずです?
- ユーザーは最初に
Pancake LPs
フラッシュ ローンを通じて 0xa361... から多額の USDT を借りました。これにより、Pancake LPs
USDT と EGD がペアになり、EGD の価格が非常に安くなりました。 - この時点で
pancakeCall()回调函数
、ユーザーは報酬を引き換えています。報酬はPancake LPs
EDG 価格の 2 つのトークンの数に基づいて計算されます。その結果、EDG の価格は非常に安くなります。これがrew
見られる計算式であり、ユーザー過剰な報酬を受け取る。
以下は、palcon に対するその後の呼び出しの簡単な紹介です。
まず、Pancake LPs
上記で借りたフラッシュ ローンを返却し、その後、対応する k 値検証を実行します (返済 = 元の金額 + 手数料であることを確認します)。
次に、PancakeSwap: WBNB-BSC-USD 2
借りたフラッシュ ローンに対して対応する承認承認を実行します。
swapExactTokensForTokensSupportingFeeOnTransferTokens 関数を呼び出して、取得したすべての EGD を USDT に交換します
次に、PancakeSwap: WBNB-BSC-USD 2
借りたフラッシュ ローンが返され、対応する k 値が検証されます。
最終的に、攻撃者は 36044 USDT の利益を得ました