当前位置:网站首页>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 :
606060405260a18060106000396000f360606040526000357c01000000000000000000000000000000000000000000000000000000009004806360fe47b11460435780636d4ce63c14605d57603f565b6002565b34600257605b60048080359060200190919050506082565b005b34600257606c60048050506090565b6040518082815260200191505060405180910390f35b806000600050819055505b50565b60006000600050549050609e565b9056Decompile 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 .
边栏推荐
猜你喜欢

MySQL的MVCC机制详解

In and exceptions, count (*) query optimization

Data Encryption Standard DES security

必须要掌握的面试重点——索引和事务(附讲B-树与B+树)

MYSQL的下载与配置 mysql远程操控

next(iter(dataloader))的一点点体会

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

transforms.RandomCrop()的输入只能是PIL image 不能是tensor

Ethereum技术架构介绍

解决pycharm里面每个字母占一格空格的问题
随机推荐
Analysis of deep security definition and encryption technology
无需人工先验!港大&同济&LunarAI&旷视提出基于语义分组的自监督视觉表征学习,显著提升目标检测、实例分割和语义分割任务!
JNI的 静态注册与动态注册
【QNX】命令
JVM entry door (1)
DoS及攻擊方法詳解
in和exsits、count(*)查询优化
JS common regular expressions
将字符串B插入字符串A,有多少种插入办法可以使新串是一个回文串
新手炒股开户选哪个证券公司比较好?怎样炒股比较安全??
In and exceptions, count (*) query optimization
陈强:阿里千亿级大规模数字商业知识图谱助力业务增长
VCD-影音光碟
Plt How to keep show() not closed
Data Encryption Standard DES security
Static registration and dynamic registration of JNI
LeetCode 面试题29 顺时针打印矩阵
必须要掌握的面试重点——索引和事务(附讲B-树与B+树)
[QNX] Command
wechat_ Solve the problem of page Jump and parameter transfer by navigator in wechat applet