当前位置:网站首页>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 :


 Zero time technology | Decompilation of the smart contract security series


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


 Zero time technology | Decompilation of the smart contract security series

The other is the byte code after reverse compilation ( You need to learn about bytecode , Not easy to understand ).


 Zero time technology | Decompilation of the smart contract security series

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 :


 Zero time technology | Decompilation of the smart contract security series


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 .
原网站

版权声明
本文为[Gentle in autumn]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/177/202206261807160887.html