Fuzzy identity
目标合约
1 | pragma solidity ^0.4.21; |
通关条件
构造一个合约,实现 name 接口且合约地址中包含 “badc0de”
题目分析
实现 name 接口是很简单的,重点在于合约地址得包含 “badc0de”
CREATE
在 ETH 中,合约地址是可以根据部署者的地址和它的 nonce(也就是发送过的交易量,根据 EIP-161,普通地址从 0 开始,合约地址从 1 开始)预先计算出来的:
1 | // 这种方法使用的是 CREATE 操作码,也是合约部署的默认方式 |
所以对于 “badc0de” 的要求,我们采用暴力搜索的方式解决–生成大量的地址,对于每个地址使用适量的 nonce 去计算合约地址,检查每个地址是否满足条件。我用 Elixir 写了个脚本进行搜索,跑了十分钟左右找出 11 条符合条件的记录。脚本如下:
1 | defmodule Ethereum.AddressGenerator do |
部署的合约如下:
1 | contract IdentifierHacker is IName { |
CREATE2
这篇文章 介绍了用 CREATE2
来解决本题的思路。
除了 CREATE
外,EVM 在 EIP-1014 中添加了一个新的操作码 CREATE2
,也可以用来生成合约地址。和 CREATE
相比, CREATE2
的好处在于其生成的合约地址并不依赖于生成者的状态(nonce)。 CREATE2
合约地址生成规则如下:
1 | keccak256(0xff ++ deployer ++ salt ++ keccak256(bytecode))[12:] |
其中 bytecode 是合约的字节码,salt 是 32 字节的随机盐值。
由于 CREATE2
不是默认生成合约的操作码,所以我们得通过一个合约来部署 IdentityHacker 合约:
1 | contract Deployer { |
部署 Deployer 后,我们就得到 IdentityHacker 的 deployer 地址了。使用 CREATE2
我们不需要生成大量地址,而是对于同一个地址暴力搜索 salt,脚本如下:
1 | defmodule Ethereum.Create2AddressGenerator do |
这份脚本花了一分钟就找到了满足条件的 salt
Public Key
目标合约
1 | pragma solidity ^0.4.21; |
通关条件
找到 owner 的 publicKey
题目分析
ECDSA 中,有了消息+签名是可以恢复出公钥的,可以参考这里和这里。
从 etherscan 上找到 owner 发送过的交易的 rawtx,可以从 rawtx 中得到签名和交易信息,有了交易信息、签名,就可以恢复出公钥。
简单处理的话,可以直接用 ethereumjs 得到答案:
1 | const EthereumTx = require('ethereumjs-tx').Transaction |
Account takeover
目标合约
1 | pragma solidity ^0.4.21; |
通关条件
得到 owner 的私钥
题目分析
写过钱包的人第一反应应该就是签名的随机数有问题。但是具体从签名推出私钥还没尝试过,所以参考了这篇题解。
查看 owner 发送的最早两笔交易,解析出来可以发现他们签名的 r 值是相同的(以太坊签名 signature = (r, s, v),其中 v 是 recovery_id):
1 | const EthereumTx = require('ethereumjs-tx').Transaction |
在 secp256k1 中有:
其中(r, s) 是签名,G 是椭圆曲线上的基点,k 是随机数,M 是消息,H(M) 表示对 M 进行 sha256,k-1 表示的是 k 的模反元素。可以看出,相同的 r 代表着相同的 k。
从 (1)(2)(3) 可以推得:
所以在 k 暴露的情况下,私钥是可以被计算出来的。接下来的任务就是尝试得到 k:
所以有:
结合 (4)(6) 就可以计算出私钥。
计算脚本如下:
1 | const bigintModArith = require('bigint-mod-arith') |
得到私钥,利用 owner 地址调用目标的 authenticate 方法即可通关。