过了一遍“数字经济CTF”的区块链题目,发现题目还可以。在这里将思路以及解题过程做一个总结,希望能够给研究的同学带来一些启发。
比赛包括两道题目,这里先将第一题的分析以及过程做一个总结。
拿到题目如下所示:
观察后发发现题目没有给传统的基础函数提示,所以我们对合约基本上是一无所知的。所以还是老样子,我们需要逆向合约了。
根据题目我们也知道获得flag的形式还是调用SendFlag函数,传入邮箱获得。
下面让我们具体分析一下题目。
扔到decompile中https://ethervm.io/decompile/ropsten/0x0c6a4790e6c8a2Fd195daDee7c16C8E5c532239B#func_func_02FA
得到下面的一些函数:
contract Contract {
function main() {
memory[0x40:0x60] = 0x80;
if (msg.data.length < 0x04) { revert(memory[0x00:0x00]); }
var var0 = msg.data[0x00:0x20] / 0x0100000000000000000000000000000000000000000000000000000000 & 0xffffffff;
if (var0 == 0x1a374399) {
// Dispatch table entry for 0x1a374399 (unknown)
var var1 = msg.value;
if (var1) { revert(memory[0x00:0x00]); }
var1 = 0x00be;
var var2 = func_02FA();
var temp0 = memory[0x40:0x60];
memory[temp0:temp0 + 0x20] = var2 & 0xffffffffffffffffffffffffffffffffffffffff;
var temp1 = memory[0x40:0x60];
return memory[temp1:temp1 + (temp0 + 0x20) - temp1];
} else if (var0 == 0x1cee5d7a) {
// Dispatch table entry for 0x1cee5d7a (unknown)
var1 = msg.value;
if (var1) { revert(memory[0x00:0x00]); }
var1 = 0x0115;
var2 = func_0320();
var temp2 = memory[0x40:0x60];
memory[temp2:temp2 + 0x20] = var2 & 0xffffffffffffffffffffffffffffffffffffffff;
var temp3 = memory[0x40:0x60];
return memory[temp3:temp3 + (temp2 + 0x20) - temp3];
} else if (var0 == 0x6bc344bc) {
// Dispatch table entry for payforflag(string)
var1 = msg.value;
if (var1) { revert(memory[0x00:0x00]); }
var1 = 0x01be;
var temp4 = msg.data[0x04:0x24] + 0x04;
var temp5 = msg.data[temp4:temp4 + 0x20];
var temp6 = memory[0x40:0x60];
memory[0x40:0x60] = temp6 + (temp5 + 0x1f) / 0x20 * 0x20 + 0x20;
memory[temp6:temp6 + 0x20] = temp5;
memory[temp6 + 0x20:temp6 + 0x20 + temp5] = msg.data[temp4 + 0x20:temp4 + 0x20 + temp5];
var2 = temp6;
payforflag(var2);
stop();
} else if (var0 == 0x8da5cb5b) {
// Dispatch table entry for owner()
var1 = msg.value;
if (var1) { revert(memory[0x00:0x00]); }
var1 = 0x01d5;
var2 = owner();
var temp7 = memory[0x40:0x60];
memory[temp7:temp7 + 0x20] = var2 & 0xffffffffffffffffffffffffffffffffffffffff;
var temp8 = memory[0x40:0x60];
return memory[temp8:temp8 + (temp7 + 0x20) - temp8];
} else if (var0 == 0x96c50336) {
// Dispatch table entry for 0x96c50336 (unknown)
var1 = 0x021f;
func_059E();
stop();
} else if (var0 == 0x9ae5a2be) {
// Dispatch table entry for 0x9ae5a2be (unknown)
var1 = 0x0229;
func_0654();
stop();
} else if (var0 == 0xd0d124c0) {
// Dispatch table entry for 0xd0d124c0 (unknown)
var1 = msg.value;
if (var1) { revert(memory[0x00:0x00]); }
var1 = 0x0240;
var2 = func_0730();
var temp9 = memory[0x40:0x60];
memory[temp9:temp9 + 0x20] = var2 & 0xffffffffffffffffffffffffffffffffffffffff;
var temp10 = memory[0x40:0x60];
return memory[temp10:temp10 + (temp9 + 0x20) - temp10];
} else if (var0 == 0xe3d670d7) {
// Dispatch table entry for balance(address)
var1 = msg.value;
if (var1) { revert(memory[0x00:0x00]); }
var1 = 0x02c3;
var2 = msg.data[0x04:0x24] & 0xffffffffffffffffffffffffffffffffffffffff;
var2 = balance(var2);
var temp11 = memory[0x40:0x60];
memory[temp11:temp11 + 0x20] = var2;
var temp12 = memory[0x40:0x60];
return memory[temp12:temp12 + (temp11 + 0x20) - temp12];
} else if (var0 == 0xed6b8ff3) {
// Dispatch table entry for 0xed6b8ff3 (unknown)
var1 = msg.value;
if (var1) { revert(memory[0x00:0x00]); }
var1 = 0x02ee;
func_076D();
stop();
} else if (var0 == 0xff2eff94) {
// Dispatch table entry for Cow()
var1 = 0x02f8;
Cow();
stop();
} else { revert(memory[0x00:0x00]); }
}
function func_02FA() returns (var r0) { return storage[0x02] & 0xffffffffffffffffffffffffffffffffffffffff; }
function func_0320() returns (var r0) { return storage[0x01] & 0xffffffffffffffffffffffffffffffffffffffff; }
function payforflag(var arg0) {
if (msg.sender != storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff) { revert(memory[0x00:0x00]); }
if (msg.sender != storage[0x01] & 0xffffffffffffffffffffffffffffffffffffffff) { revert(memory[0x00:0x00]); }
if (msg.sender != storage[0x02] & 0xffffffffffffffffffffffffffffffffffffffff) { revert(memory[0x00:0x00]); }
var temp0 = address(address(this)).balance;
var temp1 = memory[0x40:0x60];
var temp2;
temp2, memory[temp1:temp1 + 0x00] = address(storage[0x03] & 0xffffffffffffffffffffffffffffffffffffffff).call.gas(!temp0 * 0x08fc).value(temp0)(memory[temp1:temp1 + memory[0x40:0x60] - temp1]);
var var0 = !temp2;
if (!var0) {
var0 = 0x7c2413bb49085e565f72ec50a1fb0460b69cf327e0b0d882980385b356239ea5;
var temp3 = arg0;
var var1 = temp3;
var temp4 = memory[0x40:0x60];
var var2 = temp4;
var var3 = var2;
var temp5 = var3 + 0x20;
memory[var3:var3 + 0x20] = temp5 - var3;
memory[temp5:temp5 + 0x20] = memory[var1:var1 + 0x20];
var var4 = temp5 + 0x20;
var var6 = memory[var1:var1 + 0x20];
var var5 = var1 + 0x20;
var var7 = var6;
var var8 = var4;
var var9 = var5;
var var10 = 0x00;
if (var10 >= var7) {
label_053B:
var temp6 = var6;
var4 = temp6 + var4;
var5 = temp6 & 0x1f;
if (!var5) {
var temp7 = memory[0x40:0x60];
log(memory[temp7:temp7 + var4 - temp7], [stack[-6]]);
return;
} else {
var temp8 = var5;
var temp9 = var4 - temp8;
memory[temp9:temp9 + 0x20] = ~(0x0100 ** (0x20 - temp8) - 0x01) & memory[temp9:temp9 + 0x20];
var temp10 = memory[0x40:0x60];
log(memory[temp10:temp10 + (temp9 + 0x20) - temp10], [stack[-6]]);
return;
}
} else {
label_0529:
var temp11 = var10;
memory[var8 + temp11:var8 + temp11 + 0x20] = memory[var9 + temp11:var9 + temp11 + 0x20];
var10 = temp11 + 0x20;
if (var10 >= var7) { goto label_053B; }
else { goto label_0529; }
}
} else {
var temp12 = returndata.length;
memory[0x00:0x00 + temp12] = returndata[0x00:0x00 + temp12];
revert(memory[0x00:0x00 + returndata.length]);
}
}
function owner() returns (var r0) { return storage[0x03] & 0xffffffffffffffffffffffffffffffffffffffff; }
function func_059E() {
var var0 = 0x00;
var var1 = var0;
var var2 = 0x0de0b6b3a7640000;
var var3 = msg.value;
if (!var2) { assert(); }
var0 = var3 / var2;
if (var0 >= 0x01) {
var temp0 = var1 + 0x01;
storage[temp0] = msg.sender | (storage[temp0] & ~0xffffffffffffffffffffffffffffffffffffffff);
return;
} else {
var1 = 0x05;
storage[var1] = msg.sender | (storage[var1] & ~0xffffffffffffffffffffffffffffffffffffffff);
return;
}
}
function func_0654() {
var var0 = 0x00;
var var1 = 0x0de0b6b3a7640000;
var var2 = msg.value;
if (!var1) { assert(); }
var0 = var2 / var1;
memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x04;
var temp0 = keccak256(memory[0x00:0x40]);
storage[temp0] = storage[temp0] + var0;
if (msg.sender & 0xffff != 0x525b) { return; }
memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x04;
var temp1 = keccak256(memory[0x00:0x40]);
storage[temp1] = storage[temp1] - 0xb1b1;
}
function func_0730() returns (var r0) { return storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff; }
function balance(var arg0) returns (var arg0) {
memory[0x20:0x40] = 0x04;
memory[0x00:0x20] = arg0;
return storage[keccak256(memory[0x00:0x40])];
}
function func_076D() {
memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x04;
if (storage[keccak256(memory[0x00:0x40])] <= 0x0f4240) { revert(memory[0x00:0x00]); }
memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x04;
storage[keccak256(memory[0x00:0x40])] = 0x00;
storage[0x02] = msg.sender | (storage[0x02] & ~0xffffffffffffffffffffffffffffffffffffffff);
}
function Cow() {
var var0 = 0x00;
var var1 = 0x0de0b6b3a7640000;
var var2 = msg.value;
if (!var1) { assert(); }
var0 = var2 / var1;
if (var0 != 0x01) { return; }
storage[0x00] = msg.sender | (storage[0x00] & ~0xffffffffffffffffffffffffffffffffffffffff);
}
}
其中Unknown的地方是无法解析出名字的。这个也不重要,我们具体来看函数。
为了得到flag,我们当然要看 payforflag(string)
。
https://ethervm.io/decompile/ropsten/0x0c6a4790e6c8a2Fd195daDee7c16C8E5c532239B#dispatch_6bc344bc
截图中就是最关键的函数。
这里有三个限制,首先是storage[0]、storage[1]、storage[2]均需要等于msg.sender
。这真是令人头大,我们稍微查询一下这三个参数都是什么东东。
很明显这个是其他人放进去的地址,目前还不是我们的所以我们需要把它们调整为自己的。
下面看具体的函数:
现在我们翻译一下这个函数的具体含义:首先该函数定义了三个变量,首先var1我们打印出来发现是1 ether。
此时可以跳出if,然后我们得到var0 = var2/1 = var2。(注意这里以ether为单位)
而var2是我们传入的value。我们接着向下看:下面的句子意思是令合约中的storage[user]+var0.
简单来说就类似于让合约中的用户余额+var0 。
之后我们看到if (msg.sender & 0xffff != 0x525b) { return; }
。这里是需要我们使用末尾为为525b的账户。例如:
0xxxxxxcF46fA03aFFB24606f402D25A4994b3525b
。
之后便能进入该函数storage[temp1] = storage[temp1] - 0xb1b1;
。这里我们就很熟悉了。由于没有判断函数,所以这里很明显是个整数溢出。那这个有什么帮助呢?我们可以在后面函数中发现相关的作用。
下面我们来看第二个函数:
这个函数比较好理解,简单来说就是用户的合约中余额必须要大于0x0f4240
。如果满足了这个时候便会将storage[2]更变为msg.sender。就满足了我们的第一个条件。这里就是为什么我们要通过溢出来达到这个要求,所以我们调用函数1后变能满足这个条件,所以1要在这个函数前执行。
后面storage[0x02] = msg.sender | (storage[0x02] & ~0xffffffffffffffffffffffffffffffffffffffff);
我们可以算一算,与完后在或,最后还是msg.sender。
下面看第三个函数:
同样,这里传入var3,然后得到var0 。 并且需要满足>=1。之后temp0就成了1,于是我们就将storage[1]设置为msg.sender。
下面第五个函数:
同样,此时我们需要令var0==1,如何等于1呢?需要我们传入1 ether才可以。最后我们使得storage[0]为msg.sender。
这样我们就能满足调用flag的三个要求了。