国赛第二天的区块链题目,因为很多人想要wp,就写了一份完整些的。
Author
天枢Dubhe Sissel
Init
看最后发flag的邮箱是FlappyPig的大师傅出的题,厉害了。前两天参加别的比赛的时候,也逆了个合约,正好国赛用上了,可喜可贺。都是以前首席教我教的好,逃。
题目
Ropsten, 0x455541c3e9179a6cd8c418142855d894e11a288c
又给了变量名和payforflag
的函数【太棒了,不然逆这个我可看不懂】
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| contract DaysBank { mapping(address => uint) public balanceOf; mapping(address => uint) public gift; address owner; constructor()public{ owner = msg.sender; } event SendFlag(uint256 flagnum, string b64email); function payforflag(string b64email) public { require(balanceOf[msg.sender] >= 10000); emit SendFlag(1,b64email); }
|
解题过程
首先访问ropsten这个合约的地址,发现没有个源代码,遂要对合约逆向。这个工具超好用:https://ethervm.io/decompile 。可以逆出来很好看的伪代码。我不怎么会re,所以下面的一些注释都是个人理解,有不对的地方望大佬们指正。
逆向结果和注释
1 2 3 4 5 6 7 8 9
| Public Methods Method names cached from 4byte.directory. 0x652e9d91 Unknown 0x66d16cc3 profit() 0x6bc344bc Unknown 0x70a08231 balanceOf(address) 0x7ce7c990 transfer2(address,uint256) 0xa9059cbb transfer(address,uint256) 0xcbfc4bce Unknown
|
上面有三个Unknown,分别是以下三个。
- Init第一次薅羊毛
- giftOf 得到用户的gift的值
- sendFlag,得到flag的函数
理解这些,会更好的看懂下面的代码
00-20 放地址,20-40放0x01的时候,用storage访问,求的是gift的值
即 storage[keccak256(memory[0x00:0x40])] = storage[keccak256( 地址+0x01 )] 是gift[address]
00-20 放地址,20-40放0x00的时候,用storage访问,求的是balance的值
即 storage[keccak256(memory[0x00:0x40])] = storage[keccak256( 地址+0x00 )] 是balance[address]
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 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
| 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 == 0x652e9d91) { var var1 = msg.value; if (var1) { revert(memory[0x00:0x00]); } var1 = 0x009c; func_01DC(); stop(); } else if (var0 == 0x66d16cc3) { var1 = msg.value; if (var1) { revert(memory[0x00:0x00]); } var1 = 0x009c; profit(); stop(); } else if (var0 == 0x6bc344bc) { var1 = msg.value; if (var1) { revert(memory[0x00:0x00]); } var temp0 = memory[0x40:0x60]; var temp1 = msg.data[0x04:0x24]; var temp2 = msg.data[temp1 + 0x04:temp1 + 0x04 + 0x20]; memory[0x40:0x60] = temp0 + (temp2 + 0x1f) / 0x20 * 0x20 + 0x20; memory[temp0:temp0 + 0x20] = temp2; var1 = 0x009c; memory[temp0 + 0x20:temp0 + 0x20 + temp2] = msg.data[temp1 + 0x24:temp1 + 0x24 + temp2]; var var2 = temp0; func_0278(var2); stop(); } else if (var0 == 0x70a08231) { var1 = msg.value; if (var1) { revert(memory[0x00:0x00]); } var1 = 0x013a; var2 = msg.data[0x04:0x24] & 0xffffffffffffffffffffffffffffffffffffffff; var2 = balanceOf(var2); label_013A: var temp3 = memory[0x40:0x60]; memory[temp3:temp3 + 0x20] = var2; var temp4 = memory[0x40:0x60]; return memory[temp4:temp4 + temp3 - temp4 + 0x20]; } else if (var0 == 0x7ce7c990) { var1 = msg.value; if (var1) { revert(memory[0x00:0x00]); } var1 = 0x009c; var2 = msg.data[0x04:0x24] & 0xffffffffffffffffffffffffffffffffffffffff; var var3 = msg.data[0x24:0x44]; transfer2(var2, var3); stop(); } else if (var0 == 0xa9059cbb) { var1 = msg.value; if (var1) { revert(memory[0x00:0x00]); } var1 = 0x009c; var2 = msg.data[0x04:0x24] & 0xffffffffffffffffffffffffffffffffffffffff; var3 = msg.data[0x24:0x44]; transfer(var2, var3); stop(); } else if (var0 == 0xcbfc4bce) { var1 = msg.value; if (var1) { revert(memory[0x00:0x00]); } var1 = 0x013a; var2 = msg.data[0x04:0x24] & 0xffffffffffffffffffffffffffffffffffffffff; var2 = func_0417(var2); goto label_013A; } else { revert(memory[0x00:0x00]); } } function func_01DC() { memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x01; if (storage[keccak256(memory[0x00:0x40])]) { revert(memory[0x00:0x00]); } memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x00; var temp0 = keccak256(memory[0x00:0x40]); storage[temp0] = storage[temp0] + 0x01; memory[0x20:0x40] = 0x01; storage[keccak256(memory[0x00:0x40])] = 0x01; } function profit() { memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x00; if (storage[keccak256(memory[0x00:0x40])] != 0x01) { revert(memory[0x00:0x00]); } memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x01; if (storage[keccak256(memory[0x00:0x40])] != 0x01) { revert(memory[0x00:0x00]); } memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x00; var temp0 = keccak256(memory[0x00:0x40]); storage[temp0] = storage[temp0] + 0x01; memory[0x20:0x40] = 0x01; storage[keccak256(memory[0x00:0x40])] = 0x02; } function func_0278(var arg0) { memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x00; if (0x2710 > storage[keccak256(memory[0x00:0x40])]) { revert(memory[0x00:0x00]); } var var0 = 0xb1bc9a9c599feac73a94c3ba415fa0b75cbe44496bfda818a9b4a689efb7adba; var var1 = 0x01; var temp0 = arg0; var var2 = temp0; var temp1 = memory[0x40:0x60]; var var3 = temp1; memory[var3:var3 + 0x20] = var1; var temp2 = var3 + 0x20; var var4 = temp2; var temp3 = var4 + 0x20; memory[var4:var4 + 0x20] = temp3 - var3; memory[temp3:temp3 + 0x20] = memory[var2:var2 + 0x20]; var var5 = temp3 + 0x20; var var7 = memory[var2:var2 + 0x20]; var var6 = var2 + 0x20; var var8 = var7; var var9 = var5; var var10 = var6; var var11 = 0x00; if (var11 >= var8) { label_02FD: var temp4 = var7; var5 = temp4 + var5; var6 = temp4 & 0x1f; if (!var6) { var temp5 = memory[0x40:0x60]; log(memory[temp5:temp5 + var5 - temp5], [stack[-7]]); return; } else { var temp6 = var6; var temp7 = var5 - temp6; memory[temp7:temp7 + 0x20] = ~(0x0100 ** (0x20 - temp6) - 0x01) & memory[temp7:temp7 + 0x20]; var temp8 = memory[0x40:0x60]; log(memory[temp8:temp8 + (temp7 + 0x20) - temp8], [stack[-7]]); return; } } else { label_02EE: var temp9 = var11; memory[temp9 + var9:temp9 + var9 + 0x20] = memory[temp9 + var10:temp9 + var10 + 0x20]; var11 = temp9 + 0x20; if (var11 >= var8) { goto label_02FD; } else { goto label_02EE; } } } function balanceOf(var arg0) returns (var arg0) { memory[0x20:0x40] = 0x00; memory[0x00:0x20] = arg0; return storage[keccak256(memory[0x00:0x40])]; } function transfer2(var arg0, var arg1) { if (arg1 <= 0x02) { revert(memory[0x00:0x00]); } memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x00; if (0x02 >= storage[keccak256(memory[0x00:0x40])]) { revert(memory[0x00:0x00]); } memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x00; if (storage[keccak256(memory[0x00:0x40])] - arg1 <= 0x00) { revert(memory[0x00:0x00]); } memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x00; var temp0 = keccak256(memory[0x00:0x40]); var temp1 = arg1; storage[temp0] = storage[temp0] - temp1; memory[0x00:0x20] = arg0 & 0xffffffffffffffffffffffffffffffffffffffff; var temp2 = keccak256(memory[0x00:0x40]); storage[temp2] = temp1 + storage[temp2]; } function transfer(var arg0, var arg1) { if (arg1 <= 0x01) { revert(memory[0x00:0x00]); } memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x00; if (0x01 >= storage[keccak256(memory[0x00:0x40])]) { revert(memory[0x00:0x00]); }
memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x00; if (arg1 > storage[keccak256(memory[0x00:0x40])]) { revert(memory[0x00:0x00]); } memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x00; var temp0 = keccak256(memory[0x00:0x40]); var temp1 = arg1; storage[temp0] = storage[temp0] - temp1; memory[0x00:0x20] = arg0 & 0xffffffffffffffffffffffffffffffffffffffff; var temp2 = keccak256(memory[0x00:0x40]); storage[temp2] = temp1 + storage[temp2]; } function func_0417(var arg0) returns (var arg0) { memory[0x20:0x40] = 0x01; memory[0x00:0x20] = arg0; return storage[keccak256(memory[0x00:0x40])]; } }
|
因为合约比较简单,大家去找几个bank合约对照一下就看懂了。先多看智能合约,再来看逆向,会简单很多。
攻击过程
- 我们最终需要balance很大,那就需要用transfer2构造一个整数溢出。
- transfer2需要账户余额大于等于3。
- init可以让账户变为1,profit可以让账户由1变2。
- transfer 没有溢出,但只需要balance大于等于2就可以用。
- 账号1 init() 注意:这个函数的名字不知道,要用四字节码调用。
- 账号1 profit()
- 账号2 init() 注意:同1
- 账号2 profit()
- 账号1 transfer(账户2,2)
- 账号2 transfer2(账户3, 2**256-1) 给账户3,此函数有下溢
- 目标账户 getflag()
以四字节码调用合约,可以用python发包,或者写攻击合约代发,或者找一些钱包,支持修改data字段的都行。看攻击过程的话,可以看f61d的师傅的,是event里第四个地址【前三个是我。。当时收不到邮件,试了好半天,我当时是用攻击合约写的,不太清晰】。