当前位置:网站首页>Decompilation of zero time technology smart contract security series articles
Decompilation of zero time technology smart contract security series articles
2022-06-26 18:14:00 【Gentle in autumn】
Zero time technology | Decompilation of the smart contract security series
Preface
In recent years , Each large CTF(Capture The Flag, The general translation of the Chinese version of the flag contest , In the field of network security, it refers to a form of competition among network security technicians ) Blockchain attack and defense are seen in the game , And they are basically blockchain smart contract attack and defense . In this series of articles, we also focus on the attack and defense of smart contracts , To analyze the key points of smart contract attack and defense , Include contract Decompilation ,CTF Common question types and solutions , I believe it will bring readers different harvest . because CTF The smart contract source code in the competition is not open source , So we need to start from EVM The compiled opcode Reverse to get the source code logic , Then write the attack contract according to the decompiled source code , Finally get flag.
Basics
In this article, we will focus on smart contracts opcode reverse , The recommended online tools are Online Solidity Decompiler. The advantages of this website are obvious , After reverse, you will get the pseudo code of decompiled contract and the byte code of disassembled contract , And all function signatures of the contract will be listed ( The recognized function signature will be directly given , Unrecognized will give UNknown), The usage is shown in the following figure :
The first method is to enter the smart contract address , And select the network
The second method is to input the smart contract opcode
There are two contract results after reverse , One is the decompiled pseudo code ( Prefer logic code , It's easy to understand ), Here's the picture
The other is the byte code after reverse compilation ( You need to learn about bytecode , Not easy to understand ).
The tools used in this demonstration are :
Remix( Online editor ):https://remix.ethereum.org/
Metamask( Google plug-in ):https://metamask.io/
Online Solidity Decompiler( Reverse site ):https://ethervm.io/decompile/
Case a
Let's first look at a simple contract decompiler , The contract code is as follows :
pragma solidity ^0.4.0; contract Data { uint De; function set(uint x) public { De = x; } function get() public constant returns (uint) { return De; } }
After compiling opcode as follows :
606060405260a18060106000396000f360606040526000357c01000000000000000000000000000000000000000000000000000000009004806360fe47b11460435780636d4ce63c14605d57603f565b6002565b34600257605b60048080359060200190919050506082565b005b34600257606c60048050506090565b6040518082815260200191505060405180910390f35b806000600050819055505b50565b60006000600050549050609e565b9056
Decompile with online reverse tools ( The meaning of the relevant pseudo code has been detailed in the code segment ):
contract Contract { function main() { // Allocate memory space memory[0x40:0x60] = 0x60; // obtain data value var var0 = msg.data[0x00:0x20] / 0x0100000000000000000000000000000000000000000000000000000000; // Determine whether the call is consistent with set Function signature matches , If the match , Continue to implement if (var0 != 0x60fe47b1) { goto label_0032; } label_0043: // It means not to accept msg.value if (msg.value) { label_0002: memory[0x40:0x60] = var0; // obtain data value var0 = msg.data[0x00:0x20] / 0x0100000000000000000000000000000000000000000000000000000000; // Determine whether the call is consistent with set Function signature matches , If the match , Continue to implement // Dispatch table entry for set(uint256) // Here you can see set The parameter type passed in is uint256 if (var0 == 0x60fe47b1) { goto label_0043; } label_0032: // Determine whether the call is consistent with get Function signature matches , If the match , Continue to implement if (var0 != 0x6d4ce63c) { goto label_0002; } // It means not to accept msg.value if (msg.value) { goto label_0002; } var var1 = 0x6c; // This call get function var1 = func_0090(); var temp0 = memory[0x40:0x60]; memory[temp0:temp0 + 0x20] = var1; var temp1 = memory[0x40:0x60]; //if There is... After the statement return Indicates that there is a return value , The first four lines of code are all the criteria here , The final return value here is var1 return memory[temp1:temp1 + (temp0 + 0x20) - temp1]; } else { var1 = 0x5b; // The parameters passed in here var var2 = msg.data[0x04:0x24]; // call get Function var2 Parameters func_0082(var2); stop(); } } // Two functions are defined below , That is, the two function signatures listed on the website set and get // Here the function passes in a parameter function func_0082(var arg0) { //slot[0]=arg0 The parameter passed in by the function storage[0x00] = arg0; } // Global variable markers : EVM Store the global variables in the contract in a file called Storage Key value pair virtual space , // And there are corresponding organization methods for different data types , The storage method is Storage[keccak256(add, 0x00)]. // storage It can also be understood as a continuous array , be called `slot[]`, Each location can store 32 Bytes of data // Function did not pass in an argument , But there is a return value function func_0090() returns (var r0) { // It is quite clear here , Pass in the parameters of the previous function slot[0] The value of is assigned to var0 var var0 = storage[0x00]; return var0; // Eventually return var0 value } }
From the pseudocode above, you can get two functions set and get.set Function , There are obvious transmission parameters arg0, Analyze the main function main After content , It can be obtained that this function does not receive ether , And the parameter type passed in is uint256;get Function , It is obvious that no parameters have been passed in , But there is a return value , Also do not receive etheric money , adopt storage[0x00] The return value can be obtained from the related calls of set Parameters passed in the function . Finally, the source code obtained by analyzing the pseudo code is as follows :
contract AAA { uint256 storage; function set(uint256 a) { storage = a; } function get() returns (uint256 storage) { return storage; } }
Relatively speaking , The decompiled pseudo code of the contract is relatively simple , Just look at the two decompiled functions to determine the contract logic , But for contracts with more complex logic functions , The decompiled pseudo code needs to further judge the main function main() The content in .
Case 2
After a simple introduction , Let's analyze it directly CTF Decompiled code of smart contract
Contract address :https://ropsten.etherscan.io/address/0x93466d15A8706264Aa70edBCb69B7e13394D049f#code
The contract function signature and method parameter call obtained after decompilation are as follows :
The contract pseudocode is as follows ( The meaning of the relevant pseudo code has been detailed in the code segment , Mark as key ):
contract Contract { function main() { memory[0x40:0x60] = 0x80; // Judge whether the function signature is 4 byte // EVM All calls to functions in are to take `bytes4(keccak256( Function name ( Parameter type 1, Parameter type 2))` Delivered , That is to say, the function signature is keccak256 Hash before 4 byte if (msg.data.length < 0x04) { revert(memory[0x00:0x00]); } // Take the function signature , The first four bytes ( The four bytes of the function signature are expressed as 0xffffffff type ) var var0 = msg.data[0x00:0x20] / 0x0100000000000000000000000000000000000000000000000000000000 & 0xffffffff; if (var0 == 0x2e1a7d4d) { // Dispatch table entry for withdraw(uint256) var var1 = msg.value; // It means not to accept `msg.value` if (var1) { revert(memory[0x00:0x00]); } var1 = 0x00be; var var2 = msg.data[0x04:0x24]; withdraw(var2); //stop Indicates that the function has no return value stop(); } else if (var0 == 0x66d16cc3) { // Dispatch table entry for profit() var1 = msg.value; if (var1) { revert(memory[0x00:0x00]); } var1 = 0x00d5; profit(); stop(); } else if (var0 == 0x8c0320de) { // Dispatch table entry for payforflag(string,string) var1 = msg.value; if (var1) { revert(memory[0x00:0x00]); } var1 = 0x0184; var temp0 = msg.data[0x04:0x24] + 0x04; var temp1 = msg.data[temp0:temp0 + 0x20]; var temp2 = memory[0x40:0x60]; memory[0x40:0x60] = temp2 + (temp1 + 0x1f) / 0x20 * 0x20 + 0x20; memory[temp2:temp2 + 0x20] = temp1; memory[temp2 + 0x20:temp2 + 0x20 + temp1] = msg.data[temp0 + 0x20:temp0 + 0x20 + temp1]; var2 = temp2; var temp3 = msg.data[0x24:0x44] + 0x04; var temp4 = msg.data[temp3:temp3 + 0x20]; var temp5 = memory[0x40:0x60]; memory[0x40:0x60] = temp5 + (temp4 + 0x1f) / 0x20 * 0x20 + 0x20; memory[temp5:temp5 + 0x20] = temp4; memory[temp5 + 0x20:temp5 + 0x20 + temp4] = msg.data[temp3 + 0x20:temp3 + 0x20 + temp4]; var var3 = temp5; payforflag(var2, var3); stop(); } else if (var0 == 0x9189fec1) { // Dispatch table entry for guess(uint256) var1 = msg.value; if (var1) { revert(memory[0x00:0x00]); } var1 = 0x01b1; var2 = msg.data[0x04:0x24]; guess(var2); stop(); } else if (var0 == 0xa5e9585f) { // Dispatch table entry for xxx(uint256) var1 = msg.value; if (var1) { revert(memory[0x00:0x00]); } var1 = 0x01de; var2 = msg.data[0x04:0x24]; xxx(var2); stop(); } else if (var0 == 0xa9059cbb) { // Dispatch table entry for transfer(address,uint256) var1 = msg.value; if (var1) { revert(memory[0x00:0x00]); } var1 = 0x022b; var2 = msg.data[0x04:0x24] & 0xffffffffffffffffffffffffffffffffffffffff; var3 = msg.data[0x24:0x44]; transfer(var2, var3); stop(); } else if (var0 == 0xd41b6db6) { // Dispatch table entry for level(address) var1 = msg.value; if (var1) { revert(memory[0x00:0x00]); } var1 = 0x026e; var2 = msg.data[0x04:0x24] & 0xffffffffffffffffffffffffffffffffffffffff; var2 = level(var2); var temp6 = memory[0x40:0x60]; memory[temp6:temp6 + 0x20] = var2; var temp7 = memory[0x40:0x60]; //return Indicates that the function has a return value return memory[temp7:temp7 + (temp6 + 0x20) - temp7]; } else if (var0 == 0xe3d670d7) { // Dispatch table entry for balance(address) var1 = msg.value; if (var1) { revert(memory[0x00:0x00]); } var1 = 0x02c5; var2 = msg.data[0x04:0x24] & 0xffffffffffffffffffffffffffffffffffffffff; var2 = balance(var2); var temp8 = memory[0x40:0x60]; memory[temp8:temp8 + 0x20] = var2; var temp9 = memory[0x40:0x60]; return memory[temp9:temp9 + (temp8 + 0x20) - temp9]; } else { revert(memory[0x00:0x00]); } } function withdraw(var arg0) { // At the function signature , It has been given that the parameter type of this function is uint256, Judge the parameters passed in arg0 Is it equal to 2, If 2, Then continue to execute the following code , Otherwise quit if (arg0 != 0x02) { revert(memory[0x00:0x00]); } memory[0x00:0x20] = msg.sender; // Define this msg.sender The first type of , It can be done by balance The function determines , Here for balance memory[0x20:0x40] = 0x00; // Equate to require(arg0 <= balance[msg.sender]) if (arg0 > storage[keccak256(memory[0x00:0x40])]) { revert(memory[0x00:0x00]); } var temp0 = arg0; var temp1 = memory[0x40:0x60]; // Extract the main content , Can be expressed as address(msg.sender).call.gas(msg.gas).value(temp0 * 0x5af3107a4000) memory[temp1:temp1 + 0x00] = address(msg.sender).call.gas(msg.gas).value(temp0 * 0x5af3107a4000)(memory[temp1:temp1 + memory[0x40:0x60] - temp1]); memory[0x00:0x20] = msg.sender; memory[0x20:0x40] = 0x00; var temp2 = keccak256(memory[0x00:0x40]); // Can be written as storage[temp2] -= temp0, From the previous code temp0=arg0, From the previous sentence temp2 = keccak256(memory[0x00:0x40]); By upward reasoning, we can know that this is msg.sender storage[temp2] = storage[temp2] - temp0; } function profit() { memory[0x00:0x20] = msg.sender; // Define this msg.sender For the second type , It can be done by level The function determines , Here for level memory[0x20:0x40] = 0x01; // This is equivalent to require(mapping2[msg.sender] == 0) if (storage[keccak256(memory[0x00:0x40])] != 0x00) { revert(memory[0x00:0x00]); } memory[0x00:0x20] = msg.sender; // Enable first type balance Perform subsequent operations memory[0x20:0x40] = 0x00; var temp0 = keccak256(memory[0x00:0x40]); // Here is the first type balance Self plus one ,storage[arg0] += 1 storage[temp0] = storage[temp0] + 0x01; memory[0x00:0x20] = msg.sender; // Enable the second type level Perform subsequent operations memory[0x20:0x40] = 0x01; var temp1 = keccak256(memory[0x00:0x40]); // Here is the second type level Self plus one ,storage[0x80] += 1 storage[temp1] = storage[temp1] + 0x01; } // Pass in two string Parameters of type function payforflag(var arg0, var arg1) { memory[0x00:0x20] = msg.sender; // Enable first type balance Perform subsequent operations memory[0x20:0x40] = 0x00; //require(balance[msg.sender] >= 0x02540be400) if (storage[keccak256(memory[0x00:0x40])] < 0x02540be400) { revert(memory[0x00:0x00]); } memory[0x00:0x20] = msg.sender; // Enable first type balance Perform subsequent operations memory[0x20:0x40] = 0x00; // The first type balance The assignment is 0, Equate to balance[msg.sender] = 0 storage[keccak256(memory[0x00:0x40])] = 0x00; var temp0 = address(address(this)).balance; var temp1 = memory[0x40:0x60]; var temp2; temp2, memory[temp1:temp1 + 0x00] = address(storage[0x02] & 0xffffffffffffffffffffffffffffffffffffffff).call.gas(!temp0 * 0x08fc).value(temp0)(memory[temp1:temp1 + memory[0x40:0x60] - temp1]); var var0 = !temp2; // Pass in a uint256 Parameters of type function guess(var arg0) { if (arg0 != storage[0x03]) { revert(memory[0x00:0x00]); } // Judge whether the passed in parameter is the same as storage[0x03] Values match , memory[0x00:0x20] = msg.sender; // Enable the second type level Perform subsequent operations memory[0x20:0x40] = 0x01; // Judge require(mapping1[msg.sender] == 1) if (storage[keccak256(memory[0x00:0x40])] != 0x01) { revert(memory[0x00:0x00]); } memory[0x00:0x20] = msg.sender; // Enable first type balance Perform subsequent operations memory[0x20:0x40] = 0x00; var temp0 = keccak256(memory[0x00:0x40]); // Here is the first type balance Self plus one ,storage[0x80] += 1 storage[temp0] = storage[temp0] + 0x01; memory[0x00:0x20] = msg.sender; // Enable the second type level Perform subsequent operations memory[0x20:0x40] = 0x01; var temp1 = keccak256(memory[0x00:0x40]); // Here is the second type level Self plus one ,storage[0x80] += 1 storage[temp1] = storage[temp1] + 0x01; } function xxx(var arg0) { //storage[0x02] & 0xffffffffffffffffffffffffffffffffffffffff Express storage[0x02] For an address type // Determine whether the address of the originator of the caller matches if (msg.sender != storage[0x02] & 0xffffffffffffffffffffffffffffffffffffffff) { revert(memory[0x00:0x00]); } // Will the incoming uint256 Value assigned to storage[0x03] storage[0x03] = arg0; } // The two parameters passed in are address and uint256 function transfer(var arg0, var arg1) { memory[0x00:0x20] = msg.sender; // Enable first type balance Perform subsequent operations memory[0x20:0x40] = 0x00; // Here for require(balance[msg.sender] >= arg1) if (storage[keccak256(memory[0x00:0x40])] < arg1) { revert(memory[0x00:0x00]); } // Judge arg1 Is it equal to 2,require(arg1 == 2) if (arg1 != 0x02) { revert(memory[0x00:0x00]); } memory[0x00:0x20] = msg.sender; // Enable the second type level Perform subsequent operations memory[0x20:0x40] = 0x01; if (storage[keccak256(memory[0x00:0x40])] != 0x02) { revert(memory[0x00:0x00]); } // Judge the condition , by require(level[msg.sender] == 2) memory[0x00:0x20] = msg.sender; // Enable first type balance Perform subsequent operations memory[0x20:0x40] = 0x00; // Assignment operation :balance[msg.sender] = 0 storage[keccak256(memory[0x00:0x40])] = 0x00; memory[0x00:0x20] = arg0 & 0xffffffffffffffffffffffffffffffffffffffff; // Enable first type balance Perform subsequent operations memory[0x20:0x40] = 0x00; //balance[address] = arg1 storage[keccak256(memory[0x00:0x40])] = arg1; } function level(var arg0) returns (var arg0) { memory[0x20:0x40] = 0x01; memory[0x00:0x20] = arg0; return storage[keccak256(memory[0x00:0x40])]; } function balance(var arg0) returns (var arg0) { memory[0x20:0x40] = 0x00; memory[0x00:0x20] = arg0; return storage[keccak256(memory[0x00:0x40])]; } }
By analyzing the decompiled pseudo code marked in detail above , We write the contract source code :
contract babybank { address owner; uint secret; event sendflag(string base1,string base2); constructor()public{ owner = msg.sender; } function payforflag(string base1,string base2) public{ require(balance[msg.sender] >= 10000000000); balance[msg.sender]=0; owner.transfer(address(this).balance); emit sendflag(base1,base2); } modifier onlyOwner(){ require(msg.sender == owner); _; } function withdraw(uint256 amount) public { require(amount == 2); require(amount <= balance[msg.sender]); address(msg.sender).call.gas(msg.gas).value(amount * 0x5af3107a4000)(); balance[msg.sender] -= amount; } function profit() public { require(level[msg.sender] == 0); balance[msg.sender] += 1; level[msg.sender] += 1; } function xxx(uint256 number) public onlyOwner { secret = number; } function guess(uint256 number) public { require(number == secret); require(level[msg.sender] == 1); balance[msg.sender] += 1; level[msg.sender] += 1; } function transfer(address to, uint256 amount) public { require(balance[msg.sender] >= amount); require(amount == 2); require(level[msg.sender] == 2); balance[msg.sender] = 0; balance[to] = amount; } }
In the decompile contract , The points to be judged and analyzed are the logical functions and main functions in the contract main() Relevant judgments . Logical functions (withdraw,profit,payforflag,guess,xxx,transfer) Neutralizing the main function main() What needs attention is :
- memory[0x20:0x40] = 0x00 and memory[0x20:0x40] = 0x01 Represent the balance and level
- if (arg1 != 0x02) { revert(memory[0x00:0x00]); } representative require(arg1 == 2), Other conditional judgments are similar
- if (msg.sender != storage[0x02] & 0xffffffffffffffffffffffffffffffffffffffff) { revert(memory[0x00:0x00]); } Expressed as require(msg.sender == owner)
- storage[temp1] = storage[temp1] + 0x01; Expressed as level[msg.sender] += 1;
- if (msg.data.length < 0x04) { revert(memory[0x00:0x00]); } // Judge whether the function signature is 4 byte
- var var0 = msg.data[0x00:0x20] / 0x0100000000000000000000000000000000000000000000000000000000 & 0xffffffff; // Take the function signature , The first four bytes ( The four bytes of the function signature are expressed as 0xffffffff type ) ,EVM All calls to functions in are to take bytes4(keccak256( Function name ( Parameter type 1, Parameter type 2)) Delivered , That is to say, the function signature is keccak256 Hash before 4 byte
- if (var1) { revert(memory[0x00:0x00]); } // It means not to accept msg.value
- stop(); //stop Indicates that the function has no return value
- return memory[temp7:temp7 + (temp6 + 0x20) - temp7]; //return Indicates that the function has a return value
summary
The main content of this article is , Decompile smart contracts through online websites opcode One way , More suitable for novices to learn , In the next article, we will continue to share the disassembly techniques of reverse smart contracts , I hope it will help the readers .
边栏推荐
猜你喜欢
Do you know how to compare two objects
JVM entry door (1)
Dos et détails de la méthode d'attaque
How pycharm modifies multiline annotation shortcuts
MYSQL的下载与配置 mysql远程操控
(multi threading knowledge points that must be mastered) understand threads, create threads, common methods and properties of using threads, and the meaning of thread state and state transition
VCD-影音光碟
LM06丨仅用成交量构造抄底摸顶策略的奥秘
数字签名论述及生成与优点分析
Bayesian network explanation
随机推荐
刻录光盘的程序步骤
ISO documents
ZCMU--1367: Data Structure
in和exsits、count(*)查询优化
Chinese (Simplified) language pack
transforms. The input of randomcrop() can only be PIL image, not tensor
Properties file garbled
JS 常用正则表达式
LeetCode 238 除自身以外数组的乘积
Insert string B into string A. how many insertion methods can make the new string a palindrome string
I want to know. I am in Zhaoqing. Where can I open an account? Is it safe to open an account online?
vutils. make_ A little experience of grid () in relation to black and white images
No manual prior is required! HKU & Tongji & lunarai & Kuangshi proposed self supervised visual representation learning based on semantic grouping, which significantly improved the tasks of target dete
ROS查询话题具体内容常用指令
行锁分析和死锁
Map and filter methods for processing scarce arrays
腾讯钱智明:信息流业务中的预训练方法探索与应用实践
Li Kou daily question - day 28 -566 Reshape matrix
如何将应用加入到deviceidle 白名单?
Boyun, standing at the forefront of China's container industry