前言
智能合约学到现在也学了差不多2周了,也接触了一些智能合约常见的攻击手段,跟着网上的文章也学习了很多。学习的文章给的直接都是Solidity的源代码,但是:
智能合约大多数代码不公开源码,公开字节码,所以需要使用逆向工具或者人工进行逆向分析。
因此以后要强迫自己去逆向分析代码,提高自己的智能合约逆向分析能力。
但是一开始看逆向得到的伪代码还是比较迷的,因此跟着网上的一些最基本的入门的文章学习了一下,大致的理解这些逆向得到的伪代码,先有1。只要有了1,接下来就是靠自己不断的学习,不断逆向来提高分析能力了。
简单的例子
一个简单的合约:
pragma solidity ^0.4.0;
contract Data {
uint De;
function set(uint x) public {
De = x;
}
function get() public constant returns (uint) {
return De;
}
}
deploy后,去以太坊浏览器上看一下这个合约,发现字节码:
利用decomplie工具进行逆向:
给出了方法的签名和原型:
因为原本合约的代码就很简单,因此逆向得到的也很简单,加上了注释分析,比较容易理解:
contract Contract {
function main() {
//分配内存空间
memory[0x40:0x60] = 0x80;
//判断函数签名是否为4字节
if (msg.data.length < 0x04) { revert(memory[0x00:0x00]); }
//逻辑就不管了,总之这样计算后是取前4个字节,即函数的签名。
var var0 = msg.data[0x00:0x20] / 0x0100000000000000000000000000000000000000000000000000000000 & 0xffffffff;
if (var0 == 0x60fe47b1) { //set函数的函数签名
// Dispatch table entry for set(uint256)
var var1 = msg.value;
//表示不接受 `msg.value`,即这个函数不是payable修饰的。
if (var1) { revert(memory[0x00:0x00]); }
var1 = 0x62; //92
var var2 = msg.data[0x04:0x24]; //4 ~ 36
set(var2); //调用set函数设置值
stop(); //stop表示该函数无返回值
} else if (var0 == 0x6d4ce63c) { //get函数的函数签名
// Dispatch table entry for get()
var1 = msg.value;
if (var1) { revert(memory[0x00:0x00]); }
var1 = 0x76; //118
var1 = get();
var temp0 = memory[0x40:0x60];
memory[temp0:temp0 + 0x20] = var1;
var temp1 = memory[0x40:0x60];
//有return表示函数有返回值
return memory[temp1:temp1 + temp0 - temp1 + 0x20];
} else { revert(memory[0x00:0x00]); }
}
function set(var arg0) {
storage[0x00] = arg0;
}
function get() returns (var r0) { return storage[0x00]; }
}
复杂的例子
这个例子我用的是前几天做的2019 数字经济 jojo
这个题目的例子,源码如下:
pragma solidity ^0.4.24;
contract jojo {
mapping(address => uint) public balanceOf;
mapping(address => uint) public gift;
address owner;
constructor()public{
owner = msg.sender;
}
event SendFlag(string b64email);
function payforflag(string b64email) public {
require(balanceOf[msg.sender] >= 100000);
emit SendFlag(b64email);
}
function jojogame() payable{
uint geteth = msg.value / 1000000000000000000; //ether
balanceOf[msg.sender] += geteth;
}
function gift() public {
assert(gift[msg.sender] == 0);
balanceOf[msg.sender] += 100;
gift[msg.sender] = 1;
}
function transfer(address to,uint value) public{
assert(balanceOf[msg.sender] >= value);
balanceOf[msg.sender] -= value;
balanceOf[to] += value;
}
}
发布一下合约,然后逆向出来,最终是这样,加了很多注释,主要是自己也在学:
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 == 0x06211f80) {
//这个函数是payable的
// Dispatch table entry for 0x06211f80 (unknown)
var var1 = 0x0084;
func_0193();
stop();
} else if (var0 == 0x24b04905) {
// Dispatch table entry for gift()
var1 = msg.value;
//非payable
if (var1) { revert(memory[0x00:0x00]); }
var1 = 0x0084;
gift();
//无返回值
stop();
} else if (var0 == 0x6bc344bc) {
// Dispatch table entry for payforflag(string)
var1 = msg.value;
//非payable
if (var1) { revert(memory[0x00:0x00]); }
//temp0 = 0x80
var temp0 = memory[0x40:0x60];
//temp1 应该是 偏移量
var temp1 = msg.data[0x04:0x24];
//取这个动态类型的那个量,感觉可能是bytes,string或者动态数组,但是似乎存的是length,而不是动态数组的个数
//因此应该是bytes 或者 string
var temp2 = msg.data[temp1 + 0x04:temp1 + 0x04 + 0x20];
//memory[0x40:0x60] = 0x80 + (length + 31) /32 * 32 + 32
//0x40:0x60看的话,是这个string或者bytes
//存的是memory存放完这个动态的东西之后的32整数个起始地址。(包括length和内容)
memory[0x40:0x60] = temp0 + (temp2 + 0x1f) / 0x20 * 0x20 + 0x20;
//memory[0x80:0x80+0x20] = temp2;放的length
memory[temp0:temp0 + 0x20] = temp2;
var1 = 0x0084;
//msg.data[temp1 + 0x24:temp1 + 0x24 + temp2];取length长度的所有字符,即内容。
memory[temp0 + 0x20:temp0 + 0x20 + temp2] = msg.data[temp1 + 0x24:temp1 + 0x24 + temp2];
var var2 = temp0;
payforflag(var2);
stop();
} else if (var0 == 0x70a08231) {
// Dispatch table entry for balanceOf(address)
var1 = msg.value;
//非payable
if (var1) { revert(memory[0x00:0x00]); }
var1 = 0x0122;
//& 0xffffffffffffffffffffffffffffffffffffffff说明这是一个地址值
var2 = msg.data[0x04:0x24] & 0xffffffffffffffffffffffffffffffffffffffff;
var2 = balanceOf(var2);
label_0122:
var temp3 = memory[0x40:0x60];
memory[temp3:temp3 + 0x20] = var2;
var temp4 = memory[0x40:0x60];
return memory[temp4:temp4 + temp3 - temp4 + 0x20];
//return var2
} else if (var0 == 0xa9059cbb) {
// Dispatch table entry for transfer(address,uint256)
var1 = msg.value;
//非payable
if (var1) { revert(memory[0x00:0x00]); }
var1 = 0x0084;
var2 = msg.data[0x04:0x24] & 0xffffffffffffffffffffffffffffffffffffffff;
var var3 = msg.data[0x24:0x44];
transfer(var2, var3);
stop();
} else if (var0 == 0xcbfc4bce) {
// Dispatch table entry for gift(address)
var1 = msg.value;
if (var1) { revert(memory[0x00:0x00]); }
var1 = 0x0122;
var2 = msg.data[0x04:0x24] & 0xffffffffffffffffffffffffffffffffffffffff;
var2 = gift(var2);
goto label_0122;
} else { revert(memory[0x00:0x00]); }
}
function func_0193() {
memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x00;
var temp0 = keccak256(memory[0x00:0x40]);
storage[temp0] = msg.value / 0x0de0b6b3a7640000 + storage[temp0];
}
function gift() {
memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x01;
//
//slot 1好像是一个映射,mapping(address => uint) gift
//require(!gift[msg.sender])
if (storage[keccak256(memory[0x00:0x40])]) { assert(); }
memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x00;
var temp0 = keccak256(memory[0x00:0x40]);
//slot 0好像是个映射,mapping(address => uint) balance
//balance[msg.sender] = balance[msg.sender] + 100;
storage[temp0] = storage[temp0] + 0x64;
memory[0x20:0x40] = 0x01;
//gift[msg.sender] = 1;
storage[keccak256(memory[0x00:0x40])] = 0x01;
/*
require(!gift[msg.sender]);
balance[msg.sender] = balance[msg.sender] + 100;
gift[msg.sender] = 1;
*/
}
function payforflag(var arg0) {
memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x00;
//require(balance[msg.sender] <= 100000);
if (0x0186a0 > storage[keccak256(memory[0x00:0x40])]) { revert(memory[0x00:0x00]); }
var var0 = 0x7c2413bb49085e565f72ec50a1fb0460b69cf327e0b0d882980385b356239ea5;
var temp0 = arg0;
var var1 = temp0;//var1 = 0x80
var temp1 = memory[0x40:0x60]; //temp1就是memory不存传入的量之后的32整数倍的地址
var var2 = temp1;
var var3 = var2;
var temp2 = var3 + 0x20; //temp2 = memory[0x40:0x60] + 0x20
//不妨设memory不存传入的量之后的32整数倍的地址是a,那么
//memory[a:a + 0x20] = 0x20
memory[var3:var3 + 0x20] = temp2 - var3;
//memory[a+0x20:a+0x40] = 内容的长度,length
//这里temp2 = a + 0x20
memory[temp2:temp2 + 0x20] = memory[var1:var1 + 0x20];
//再往后放0x20
var var4 = temp2 + 0x20;
//var5 = 0x80 + 0x20
var var5 = var1 + 0x20;
//var6 也是内容的长度,length
var var6 = memory[var1:var1 + 0x20];
//var7 也是内容的长度,length
var var7 = var6;
//var8 = a + 0x20 + 0x20
var var8 = var4;
//var9 = 0x80 + 0x20
var var9 = var5;
var var10 = 0x00;
//var7是内容的长度
//好吧下面我就看不懂了,鬼知道这到底是一团什么东西。。。给孩子看傻了,一看代码tm的
//原来是emit。。。。草!!!!!!!!!!!!
if (var10 >= var7) {
label_026C:
var temp3 = var6;
var4 = temp3 + var4;
var5 = temp3 & 0x1f;
if (!var5) {
var temp4 = memory[0x40:0x60];
log(memory[temp4:temp4 + var4 - temp4], [stack[-6]]);
return;
} else {
var temp5 = var5;
var temp6 = var4 - temp5;
memory[temp6:temp6 + 0x20] = ~(0x0100 ** (0x20 - temp5) - 0x01) & memory[temp6:temp6 + 0x20];
var temp7 = memory[0x40:0x60];
log(memory[temp7:temp7 + (temp6 + 0x20) - temp7], [stack[-6]]);
return;
}
} else {
label_025D:
//temp8 = 0x00
var temp8 = var10;
//memory[a + 0x20 + 0x20:a + 0x20 + 0x20+0x20] = memory[0x80 + 0x20:0x80 + 0x20+0x20];
memory[temp8 + var8:temp8 + var8 + 0x20] = memory[temp8 + var9:temp8 + var9 + 0x20];
//var10 = 0x20;
var10 = temp8 + 0x20;
if (var10 >= var7) { goto label_026C; }
else { goto label_025D; }
}
}
function balanceOf(var arg0) returns (var arg0) {
memory[0x20:0x40] = 0x00;
memory[0x00:0x20] = arg0;
return storage[keccak256(memory[0x00:0x40])];
/*
return balance[arg0];
*/
}
function transfer(var arg0, var arg1) {
memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x00;
//require(balance[msg.sender] >= arg1);
if (arg1 > storage[keccak256(memory[0x00:0x40])]) { assert(); }
memory[0x00:0x20] = msg.sender;
memory[0x20:0x40] = 0x00;
var temp0 = keccak256(memory[0x00:0x40]);
var temp1 = arg1;
//balance[msg.sender] = balance[msg.sender] - arg1;
storage[temp0] = storage[temp0] - temp1;
memory[0x00:0x20] = arg0 & 0xffffffffffffffffffffffffffffffffffffffff;
var temp2 = keccak256(memory[0x00:0x40]);
storage[temp2] = temp1 + storage[temp2];
}
function gift(var arg0) returns (var arg0) {
memory[0x20:0x40] = 0x01;
memory[0x00:0x20] = arg0;
//return gift[arg0];
return storage[keccak256(memory[0x00:0x40])];
}
}
除了payforflag那个函数有些看不懂,后来才发现这样的其实题目本身都给了:
pragma solidity ^0.4.24;
contract jojo {
mapping(address => uint) public balanceOf;
mapping(address => uint) public gift;
address owner;
constructor()public{
owner = msg.sender;
}
event SendFlag(string b64email);
function payforflag(string b64email) public {
require(balanceOf[msg.sender] >= 100000);
emit SendFlag(b64email);
}
............
总结
又水了一篇博客,主要还是记录下自己的关于区块链的第一个和第二个逆向分析的例子,之后自己就要开始提升逆向分析能力和学习区块链的各种安全方面的问题了。