监守自盗?Wintermute 黑客入侵分析

FastDaily2022-09-30 00:30

本文属于老雅痞原创文章,转载规矩不变,给我们打声招呼~

转载请微信联系:yaoyaobigc,更多 DAO、Web3、NFT、Metaverse 资讯请关注老雅痞👇

导读


今日FastDaily共推送 3 篇文章。

本研究将通过分析智能合约本身、其交互和被认为是黑客发起的交易来驳斥关于Wintermute 黑客攻击的观点。在下面的分析中,我们将看到执行这一黑客行为所需的知识排除了黑客是一个随机的外部实体的可能性。


体育是大生意,又是快到世界杯了,气氛已经到这了,不聊聊粉丝球迷 token 就不礼貌了。推荐阅读本文,我们的原创分析文章给你捋一捋 Web3 的世界,体育经济怎么玩。


TokenScript 是一个以多种方式提高区块链代币功能、安全性和可用性的框架。第二条可以帮助你了解这个希望帮助 Token 摆脱既定框架的应用。


周五的大佬问 DAO海报新鲜出炉!本周我们打算聊一聊Game 怎么 Fi,欢迎大家来玩。参加方法很简单,找到文中的活动海报,扫码,添加到我的会议就行,周五晚 9 点,到点儿记得来腾讯会议,我们不见不散~~


RR 丨编译

信息来源自 medium ,略有修改,作者Librehash



关于Wintermute 黑客攻击目前普遍的说法是,调用了“被攻击的”Wintermute 智能合约的 EOA 本身是因为该团队使用了一个有问题的在线虚构地址生成工具而被入侵的。其想法是,通过恢复 EOA 的私钥,攻击者能够在应该具有管理访问权限的 Wintermute 智能合约上进行调用。

 

本研究将通过分析智能合约本身、其交互和被认为是黑客发起的交易来驳斥这一观点。在下面的分析中,我们将看到执行这一黑客行为所需的知识排除了黑客是一个随机的外部实体的可能性。

 

换句话说,EOA 发起的相关交易清楚地表明,黑客很可能是 Wintermute 团队的内部成员。


 

在分析 Wintermute 被黑客攻击后发布的一些报告时,有人声称被攻击的 EOA (0x0000000fE6A514a32aBDCDfcc076C85243De899b) 能够通过 0x178979ae 方法针对 Wintermute 智能合约执行交易。



由于某种原因,我们在这里检查的 Wintermute 智能合约没有上传的、经过验证的代码。



这本身就表明了项目透明度的问题。人们会期望任何部署到区块链上的负责管理用户 / 客户资金的智能合约都是公开验证的,以便让公众有机会检查和审计 Solidity 代码。


在任何一种情况下,我们都可以通过 Solidity 对智能合约的字节码进行反编译。为此,我们不打算使用 Etherscan 提供的工具。相反,我们将选择' dedaub '提供的智能合约工具。



由于我们没有已部署的智能合约的原始来源,所以我们从字节码反编译中得到的最好结果是通过字节码指令解释被粗略翻译的 4 字节十六进制函数签名。


切换到由 dedaub 提供的“函数”面板,我们可以快速筛选出反编译的字节码,找到据称被入侵的 EOA 所调用的函数。



下面是这部分反编译字节码的截图:



具体来说,如果我们访问该代码,导致许多人相信 EOA 具有管理功能的问题区域是在该定义函数下面的一行:

 

function 0x178979ae(uint256 varg0, uint256 varg1, uint256 varg2) public nonPayable {

    require(msg.data.length - 4 >= 96);

    require(varg0 == address(varg0));

    require(varg2 <= 0xffffffffffffffff);

    require(4 + varg2 + 31 < msg.data.length);

    require((?).length <= 0xffffffffffffffff, 65);

    v0 = new bytes[]((?).length);

    require(!((v0 + (0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0 & ((?).length + 31 & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0) + 32 + 31) < v0) | (v0 + (0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0 & ((?).length + 31 & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0) + 32 + 31) > 0xffffffffffffffff)), 65);

    require(4 + varg2 + (?).length + 32 <= msg.data.length);

    CALLDATACOPY(v0.data, 4 + varg2 + 32, (?).length);

    MEM[32 + (v0 + (?).length)] = 0;

if (0xff & _setCommonAdmin[msg.sender]) {

 

在上面的代码摘录中,最后一个是我们要仔细研究的内容。

 

据说变量_setCommonAdmin 被设置为包含 EOA 0x0000000fE6A514a32aBDCDfcc076C85243De899b。幸运的是,通过使用 dedaub 智能合约工具,我们可以对其进行测试。

 

然而,首先我们需要仔细检查反编译的代码,看看该变量的任何值是否已被存储在智能合约本身中 ( 每当创建这类性质的变量时,通常都会出现这种情况 )。

 

我们很幸运,看起来是这样的。



我们分离出来的具体代码行如下:


mapping (uint256 => [uint256]) _setCommonAdmin

 

代码注释指出该变量的值在智能合约中被设置为 STORAGE[0x2]。因此,让我们检查一下,当我们将所谓受到攻击的 EOA 输入到智能合约存储的 0x2 部分时,会得到什么结果。



输入 EOA 值 (0x0000000fE6A514a32aBDCDfcc076C85243De899b) 将得到一个“boolean”值。这意味着在输入此值后,我们将得到整数'0'或'1'。如果结果为'0',则表示 false;如果结果为'1',则表示为 true。本质上,false 意味着_setCommonAdmin 变量没有将有问题的 EOA 存储为其值。'1'或' true '则表示相反的结果。

 

让我们看看下面的结果:



正如我们在上面的截图中看到的,得到的 boolean 值是' 0 ' 或 false。这意味着 EOA 的地址没有定义为_setCommonAdmin 变量的值。

 

此外,值得注意的是,代码中有用于存储_signers、owner_8 和 owner_3 的变量。因此,智能合约被设计为允许“普通管理员”单方面操作的可能性似乎微乎其微。

 

再深挖一点

 

让我们来看看其中一笔交易。

 

TX ID: 0xd2ff7c138d7a4acb78ae613a56465c90703ab839f3c8289c5c0e0d90a8b4ce16

 

该交易显示,从 Wintermute 智能合约地址向 0x0248 智能合约(据说是由 Wintermute 黑客创建和控制的)转移了 1348 万 USDT。

 

但是,如果我们观察一下这笔交易的跟踪执行,就会发现这笔转账比仅仅从一个智能合约向另一个智能合约发送资金要复杂一些。



我们要做的第一件事是分解 EOA 发送到 0x0000000ea 智能合约的 calldata。

 

下面是该交易的 calldata:

 

0x178979ae0000000000000000000000001111111254fb6c44bac0bed2854e76f90643097d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c42e95b6c8000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000c40d07f649000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000003b6d03400248f752802b2cfb4373cc0c3bc3964429385c2600000000000000000000000000000000000000000000000000000000

 

下面是上述 calldata 的分解:

 

  1. 0x178979ae- 这是 Wintermute 智能合约调用的初始方法

  2. 1111111254fb6c44bac0bed2854e76f90643097d- 这是 1inch v4: Router 的智能合约地址,它负责协调 Wintermute 智能合约和 0x0248 智能合约之间的 swap。

  3. c40d07f6490- 这是从一个智能合约传输到另一个智能合约的十进制金额的十六进制表示

  4. 0x00000000000000003b6d03400248f752802b2cfb4373cc0c3bc3964429385c26- 在 ethtx.info 的交易执行跟踪中,我们可以看到这个值被分配给了参数,pool 被附加到了 unoswap 函数。仔细观察,我们可以看到值中嵌入了 0x0248f752802b2cfb4373cc0c3bc3964429385c26 合约地址。

 

考虑到上面内容,让我们看一看负责处理从 Wintermute 智能合约到 0x0248 合约的资金协调 / 交换的中间智能合约。

 

该合约地址为:0x1111111254fb6c44bac0bed2854e76f90643097d

 

下面是 EOA、Wintermute 智能合约、1inch v4: Router 和 0x0248 智能合约地址之间交互的可视化图。



该合约中的函数将提供充分的证据,证明由 EOA 执行的交易很可能是由该团队自身进行的。

 

该智能合约代码遵循了 EIP-1271 标准,该标准提供了一种验证智能合约签名的方法。通过检查 AggregationRouterV4 智能合约代码,我们可以看到,如果调用者没有验证他们的权限,资金就不能被移动。

 

在 AggregationRouterV4 智能合约中,当调用者是另一个智能合约时,验证签名的函数如下:

 

interface IERC1271 {

    function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue);

}

 

在代码的后面,我们可以看到需要对 AggregationRouterV4 智能合约上的调用进行验证 ( 从_validate 函数获得'success' /boolean 值'1')。

 

代码如下:

 

function _validate(address signer, bytes32 orderHash, bytes calldata signature) private view {

    if (ECDSA.tryRecover(orderHash, signature) != signer) {

  (bool success, bytes memory result) = signer.staticcall(

   abi.encodeWithSelector(IERC1271.isValidSignature.selector, orderHash, signature)

  );

  require(success && result.length == 32 && abi.decode(result, (bytes4)) == IERC1271.isValidSignature.selector, "LOP: bad signature");

}

 

回到 Wintermute 智能合约,我们可以看到合约本身中已经定义了一个 isValidSignature。



从以下未经验证的合约字节码的反编译 ABI:

 

function isValidSignature(bytes32 varg0, bytes varg1) public nonPayable {

    require(msg.data.length - 4 >= 64);

    require(varg1 <= 0xffffffffffffffff);

    require(4 + varg1 + 31 < msg.data.length);

    require((?).length <= 0xffffffffffffffff, 65);

    v0 = new bytes[]((?).length);

    require(!((v0 + (0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0 & ((?).length + 31 & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0) + 32 + 31) < v0) | (v0 + (0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0 & ((?).length + 31 & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0) + 32 + 31) > 0xffffffffffffffff)), 65);

    require(4 + varg1 + (?).length + 32 <= msg.data.length);

    CALLDATACOPY(v0.data, 4 + varg1 + 32, (?).length);

    MEM[32 + (v0 + (?).length)] = 0;

    v1 = 0xdf2(v0, varg0);

    return v1 & 0xffffffff00000000000000000000000000000000000000000000000000000000;

}

 

该智能合约还清楚地将_signers 定义为一个独立的变量,合约的存储空间中包含与之关联的值。



此外,在 Solidity 中用于' transfer '的 4 字节 keccak256 abi 编码是 0xa9059cbb,这是在函数 0x394b1de1 中指定的 (EOA 未将其调用到 Wintermute 智能合约 )。

 

作为参考 ( 对于那些好奇交易的签名被包含在哪里的人来说 ),看看我们在这里观察到的主要交易的原始 TX 十六进制就知道了。



进一步的可疑活动

 

让我们回到 Etherscan,特别是看一下所谓的被破坏的智能合约——0x00000000ae347930bd1e7b0f35588b92280f9e75

 

我们将改变 Etherscan 的范围,使它只显示使用该智能合约中涉及 USDT 代币的所有交易。

 

这会给我们带来一个非常有趣的结果:



正如我们在上面的截图中看到的,据称被入侵的 Wintermute 智能合约 (0x0000000ae) 收到了 Kraken 和币安热钱包的两笔存款。我们可以有把握地假设,这种转账一定是由该团队控制的交易所账户发起的。

 

在 Wintermute 智能合约收到超过 1300 万 USDT 资金后不到一分钟,所有 Tether 都被从钱包手动转移到了 0x0248 智能合约中。正如我们前面看到的,这次转账是由 0x0000000fe 普通钱包地址发起的。

 

这一系列事件使我们提出以下问题:

 

  1. 该团队从两个不同的交易所 ( 币安和 Kraken) 向他们的智能合约发起了两次取款,这距离他们被入侵的时间不到 2 分钟,这是否合理?

  2. 如果我们对第一个问题的回答是“否”,那么攻击者是否有可能不仅入侵了该团队的智能合约,还入侵了他们的交易所账户?如果是这样,攻击者是如何得知这些交易所账户的存在的?


除了上面提出的两个问题,我们还必须问自己,Wintermute 是如何被攻破的,以及黑客如何掌握我们上面看到的智能合约之间的协调的方法的。

 

这些都是该团队完全没有回答的问题。相反,Evgeny 在 Twitter 上默认了虚拟地址生成器工具私钥泄露的理论 ( 以一种相当不令人信服的方式 )。



其他推文都相当不连贯。例如:



Evgeny 在黑客入侵之后给出的解释是匆忙且草率的,这给人的印象是他和 / 或 Wintermute 团队对他们能够在几乎没有审查的情况下潜在地完成 1.6 亿美元以上的抢劫感到松了一口气。


如果 Wintermute 团队愿意反驳这篇报道,欢迎任何回应。希望回应能解决本分析中提出的一些问题和担忧。


【免责声明】市场有风险,投资需谨慎。本文不构成投资建议,用户应考虑本文中的任何意见、观点或结论是否符合其特定状况。据此投资,责任自负。

来源链接

相关Wiki

相关标签