Developers Club geek daily blog

1 year, 3 months ago
Intel® Tamper Protection Toolkit — the obfustsiruyushchy compiler and means of check of integrity of a code
Quite recently the Intel company let out very interesting tool kit for software developers allowing to add protection of a program code against cracking and it is essential to complicate life to hackers of programs. This set includes the obfustsiruyushchy compiler, means for creation of the file of the signature used for check of integrity of the loaded dynamic libraries and also library of functions of check of integrity and additional useful tools. Intel Tamper Protection Toolkit beta can be downloaded absolutely free of charge on the website Intel.

Obfustsiruyushchy compiler


One of the Tamper Protection Toolkit tools is the obfustsiruyushchy compiler — iprot. By means of this tool it is possible to protect executable code from the static and dynamic analysis / change. The self-ciphered, self-modified code resisting modifying, static analysis and functionally equivalent to lead-in code is result of operation of the obfustsiruyushchy compiler.

The Obfustsiruyushchy compiler works with functions of dynamic libraries. However very often applications are written so that the confidential code is located in the application. In order that to protect this code, it is necessary to make small refactoring of the application: to select confidential functions in separate dynamic library.

Intel® Tamper Protection Toolkit — the obfustsiruyushchy compiler and means of check of integrity of a code

In results of operation of the obfustsiruyushchy compiler other dynamic library which functions will be subjected to processing will be created. These functions also should be used further for work.

Naturally, protection of a code does not happen free. One of payments for protection is deceleration of work of a code and increase in code size of functions. The obfustsiruyushchy compiler has pair of parameters by means of which it is possible to control a ratio protection/performance for a code. Falling of performance can be compensated by refactoring of the source code, for example, to use inlayn functions, optimization on code size. It is possible to try to change parameters of the obfustsiruyushchy compiler - mutation-distance and - cell-size.

Parameter - mutation-distance controls the frequency to which there is a most modification of a code. The smaller distance gives us the best protection but worsens performance. It is possible to try several different values of this parameter to achieve performance optimum in your opinion.

Parameter - cell-size controls the size of the decoded / open code at a given time. The smaller size of a cell of a code means the best protection, however worsens performance. Increasing the size of a cell it is possible to improve significantly performance, but also it is essential to worsen protection of a code since big code locations will be available (to be in open form) for the analysis. Having tried different values of this parameter, it is possible to achieve optimum performance.

Thus it is possible to tell that creation protected and in too time of a productive code not a simple task. For its implementation can be required not only a variation of parameters of the obfustsiruyushchy compiler, but also also refactoring of the source code to achieve the best performance and the smaller size of the source code.

It is necessary to notice, not any code can be processed by the obfustsiruyushchy compiler. There are restrictions for the source code, such as lack of relokation (relocations), indirect jumps (inderect jumps) or challenges of functions from other libraries which code is unavailable at the time of processing by the obfustsiruyushchy compiler.

Further we will review an example in which we will try to fight with some problems of incompatibility of a code which can meet in real life.

Check of integrity of a code of dynamic library


To be convinced that the loadable module of dynamic library was not exposed to modifications by Tamper Protection Toolkit contains a special set of means. The instrument of creation of the signature of dynamic library – codebind and small library of functions for check of integrity of library – codeverify.lib.

Intel® Tamper Protection Toolkit — the obfustsiruyushchy compiler and means of check of integrity of a code

The instrument of creation of the digital signature of dynamic library (codebind) accepts on an input a name of library and private DSA a key which can be generated, for example, by means of OpenSSL* library. As a result the additional file (secure box) which will be used further for check of integrity of the related dynamic library turns out.

Check of integrity of dynamic library is performed by means of several functions which are part of Tamper Protection Toolkit. The application needs to be modified, having added API calls. Functions of this API allow to check integrity of dynamic library even before memory load (static check), and also to check whether the code of library in the course of execution of the program (dynamic check) was exposed to modification.

Library of cryptographic functions


Several basic cryptographic functions are a part of Tamper Protection Toolkit:
  • Functions of creation of the unidirectional hash: HMAC SHA256
  • Functions of symmetric key encryption: AES (CTR/GCM)
  • And some other.

All these functions can be used in a confidential code and are processed by the obfustsiruyushchy compiler which is a part of Tamper Protection Toolkit.

Thus, by means of tools and the Tamper Protection Toolkit functions it is possible to create reliable protection of a confidential code or information which is contained in the user application. The Obfustsiruyushchy compiler allows to create the self-ciphered, self-modified code resisting modifying and static analysis. The Codebind tool will help with creation of the file of the digital signature, and the library of functions of check of integrity will help to check whether functions of dynamic library as on a disk, before loading, and after loading of a code in memory were modified. Function of crypto library will be helped with creation of cryptographic algorithms and by protection them by means of an obfustsiruyushchy kompliyator.  

Example of an obfuskation of a code


In this example we will show as it is possible to protect a code by means of the obfustsiruyushchy compiler which is a part of Intel Tamper Protection Toolkit. And also we will show how it is possible to get rid of some problems which can arise in the course of a code obfuskation.

That it is required to us for this example
  • Understanding of basic bases With/with ++. Acquaintance in general to relokation (relocations) and indirect jumps (indirect jump).
  • With/with ++ compiler, for example, Intel; Compiler or Microsoft Visual Studio *
  • Intel Tamper Protection Toolkit

In this example commands of the compiler Visual Studio are used, however similarly it is possible to collect a code and any other compiler.

The Obfustsiruyushchy compiler entering a set of means Intel Tamper Protection Toolkit accepts on an input a way to dynamic library (dll/so), function name which it will be necessary to obfustsirovat also parameters of an obfuskation. Therefore for work we at first should create dynamic library. The tutorials\obfuscation_tutorial folder can find an example for creation of dynamic library on a disk.

For assembly of dynamic library we will use Visual Studio*. Start the command line of the Visual Studio, make the following team:

cl /GS- /GR- src_compatible.c /link /DLL /NOENTRY /OUT:.\src_compatible.dll

As a result of execution of this command src_compatible.dll dynamic library has to appear. For compilation the following options were used
  • / GR - — this option switches off check of object types in the course of execution
  • / GS-— this option switches off overflow checking.
  • / NOENTRY — this option turns off creation of main entry in dynamic library.

That the test earned, compile the test loadutil.c and related.h application.

cl loadutil.c /link /OUT:.\loadutil.exe

After the test application gathers, it is possible to start the test and to check that the dynamic library which is brought together by us works:

loadutil src_compatible.dll

The application has to write Called get_symbol () to function successfully in a command window.

Further, we will create the obfuskirovanny version of our dynamic library by means of the following command:

iprot src_compatible.dll -o obfuscated.dll get_symbol

On a disk there has to be an obfuscated.dll library which name we also will give on an input of our test application:

loadutil obfuscated.dll

The application again has to write Called get_symbol () to function successfully in a command window. Thus, the obfustsirovanny library is functionally completely equivalent not obfustsirovanny library. However, if you look at dynamic libraries by means of the HEX editor, you will find out that it is almost impossible to understand a code of obfustsirovanny library.
So, we constructed our first elementary obfustsirovanny dynamic library. Let's look what problems and difficulties can will meet at an obfustsirovaniye more difficult With/with ++ a code and as to fight against it.

Councils how to bypass reefs


The Obfustsiruyushchy compiler which is a part of Tamper Protection Toolkit has several restrictions for a code which it tries to process, namely:
  • The code generating relocation (relocations)
  • The code generating indirect jumps (inderect jumps)
  • The code generating PIC global links (specifics of Anroid *)

Language C contains very many constructions which can generate indirect jumps and relocation. Further we will look on some examples of such constructions and methods as it is possible to bypass generation of an unwanted code.

Let's review the example containing a code which cannot process the obfustsiruyushchy compiler — src_incompatible.c. It can be taken in the tutorials/obfuscation_tutorial folder.
At first we will construct dynamic library of this file:

cl /GS- /GR- src_incompatible.c /link /DLL /NOENTRY /OUT:.\Incompatible.dll

Further, obfuskiruy one of functions of this dynamic library:

iprot Incompatible.dll -o Obfuincompatible.dll get_symbol

As a result, you have to see approximately following message:

[parsing_flow-1]: Processing 'get_symbol' …
[warning-1]: warning: minimal mutations detected at top level loop; adding more
[scheduling-1]: Setting top level mutation distance: 1
[analysis-1]:
[PROC 0:0x10001010:12 <-0]

iprot: unsupported memory reference with relocation in acquired code at 0x1000101c:
mov al, byte ptr [eax+10002000h]


The Obfustsiruyushchy compiler met relocation and could not continue processing. It occurred because as get_symbol () the appeal to global variable of alphabet is used. The compiler generates relocation which the obfustsiruyushchy compiler cannot process. One of methods to get rid of relocation — to tell the pointer as parameter at function call:
Solution No. 1
  char API get_symbol(char const* alphabet_data, unsigned int alphabet_size, unsigned int s_idx)
  {
      if (s_idx < alphabet_size)
          return alphabet_data[s_idx];

      return ' ';
  }


It is possible to arrive differently, instead of global data to use local variable.
Solution No. 2
  char API get_symbol_second(unsigned int s_idx)
  {
      char alphabet_local[26];
      alphabet_local[0] = 'a';
      alphabet_local[1] = 'b';
      alphabet_local[2] = 'c';
      alphabet_local[3] = 'd';
      alphabet_local[4] = 'e';
      alphabet_local[5] = 'f';
      alphabet_local[6] = 'g';
      alphabet_local[7] = 'h';
      alphabet_local[8] = 'i';
      alphabet_local[9] = 'j';
      alphabet_local[10] = 'k';
      alphabet_local[11] = 'l';
      alphabet_local[12] = 'm';
      alphabet_local[13] = 'n';
      alphabet_local[14] = 'o';
      alphabet_local[15] = 'p';
      alphabet_local[16] = 'q';
      alphabet_local[17] = 'r';
      alphabet_local[18] = 's';
      alphabet_local[19] = 't';
      alphabet_local[20] = 'u';
      alphabet_local[21] = 'v';
      alphabet_local[22] = 'w';
      alphabet_local[23] = 'x';
      alphabet_local[24] = 'y';
      alphabet_local[25] = 'z';
      if (s_idx < sizeof(alphabet_local))
          return alphabet_local[s_idx];
      return ' ';
  }

Further, obfustsiruy our library with the following command:

iprot Incompatible.dll -o Obfuincompatible.dll get_next_state

As a result, the following has to turn out:

[parsing_flow-1]: Processing 'get_next_state' …
[warning-1]: warning: minimal mutations detected at top level loop; adding more
[scheduling-1]: Setting top level mutation distance: 2
[analysis-1]:
[PROC 0:0x10001030:18 <-0]

iprot: unsupported indirect jump in acquired code at 0x10001055:
jmp dword ptr [100010A0h+edx*4]


The Obfustsiruyushchy compiler met indirect jump which could not process.
If to glance in get_next_state function code () it is possible to see use of switch which generates indirect jump. It is possible to get rid of indirect jump easily using if-else if.
Solution
  my_state API get_next_state(my_state in_state)
  {
     if(ST_UNINITIALIZED == in_state)
        return ST_CONNECTING;
     if(ST_CONNECTING == in_state)
      return ST_NEGOTIATING;
     if(ST_NEGOTIATING == in_state)
        return ST_INITIALIZING;
     if(ST_INITIALIZING == in_state)
          return ST_PROCESSING;
       if(ST_PROCESSING == in_state)
          return ST_DISCONNECTING;
      if(ST_DISCONNECTING == in_state)
          return ST_FINISHED;
       return ST_UNINITIALIZED;
  }


To get rid of generation of a PIC code for Android, it is possible to use a compilation option - fno-pic.

Example of check of integrity of a code


In this example we will show how to use functions of the software, the called binding (binding) and check of integrity (integrity verification) of the binary data provided to Tamper Protection Toolkit. These functions can help you to complicate essentially life to hackers of your program code. Function of check of integrity checks data on a disk, and also binary code loaded into a program memory. Following steps from this example, you learn as it is possible to use binding and check of a code.

That it is required to us for this example
  • Understanding of basic bases With/with ++. Acquaintance in general to relokation (relocations) and indirect jumps (indirect jump).
  • With/with ++ compiler, for example, Intel Compiler or Microsoft Visual Studio *
  • OpenSSL * library for key generation.
  • Intel Tamper Protection Toolkit

In this example commands of the compiler Visual Studio are used, however similarly it is possible to collect a code and any other compiler.

Let's begin with assembly of the first component of our example — dynamic library module.dll. The source code can be found on a disk in the tutorials/code_verification folder.

cl module.c  /link  /DLL /OUT:module.dll

Further we will collect the application using functions from dynamic library.

cl sample_app_without_verification.cpp /link module.lib

If to start the collected application, then you have to see the following

> sample_app_without_verification
sum(3,5) returns 8
sum(3,5) + global_array[3] returns 12


Now everything is ready for protection of our dynamic library by means of functions of binding and check of integrity. By means of one of means of Tamper Protection Toolkit we will create the special file of the signature with the .sb expansion called by "secure box". This file contains the data used by functions of a tulkit for check of integrity of dynamic library module.dll.
For a start we will use OpenSSL to generate necessary keys

md keys
openssl dsaparam -out keys/dsaparam.pem 2048
openssl gendsa -out keys/prikey.pem keys/dsaparam.pem
openssl dsa -in keys/prikey.pem -outform der -out keys/pubkey.der -pubout

For generation of the file of the signature we will use the codebind.exe program. Input data for function of linkng of a code with the signature is the dynamic library and the private key generated in advance. The file of the signature — "secure box" is result of process of binding. The file name of the signature and its expansion is any, at choice of the user. In this example expansion ".sb" will be used. The command for binding looks so:

codebind -i <path to the dll/so file> -k <path to private key> -o <output path to secure box file>

To connect our dynamic library by means of the private key generated by means of OpenSSL start the following command:

codebind -i module.dll -k keys/prikey.pem -o module.sb

If your platform does not support "Intel Secure Key" you will see the following message:

codebind: Intel Secure Key (RDRAND instruction) is not supported.
Use "-seed" program option


In this case execute the following command using a key "-seed" with a random number:

codebind -i module.dll -k keys/prikey.pem -o module.sb --seed 0xabba

In case of successful binding, in a directory the module.sb file has to appear, we will use it for check of integrity of our dynamic library.

So, by this moment we have a dynamic library — module.dll containing functionality which we want to protect. The sample_app_without_verification application causing functions from our dynamic library. Couple of keys: public and private, we used the last for creation of the file of the signature — "module.sb".

Adding of a code which will check integrity of our dynamic library statically and dynamically in the course of a challenge of functions from it will be the following step of our example.

The first step on this way — to turn a public key into a type which can be used in an integrity check code. For this purpose it is possible to use the tool under the name "bin2hex" which accepts a public key on an input and generates the text file (".h") which contains a public key, in a type suitable for the "C" compilation.
bin2hex keys/pubkey.der pubkey.h


The subsequent steps will help us to add check of integrity of our dynamic library to our sample_app_without_verification.cpp application. After all pieces of a code will be added to the source file, the code matching the sample_app_with_verification.cpp file has to turn out.
So, we will begin with inclusion of files of headings necessary to us:

    #include "codeverify.h"
    #include "pubkey.h"
    #include <fstream>
    #include <memory>
    #if defined(_WIN32)
    #include <windows.h>
    #else
    #include <dlfcn.h>
    #endif


Further, we will add codes for error handling and the declaration of the necessary variables and functions:

Codes of errors and declaration
    enum {
        V_STATUS_OK                      = 0,   /*!< Indicates no error */
        V_STATUS_NULL_PTR                = -1,  /*!< Input argument is null pointer */
        V_STATUS_BAD_ARG                 = -2,  /*!< Bad input argument */
        V_STATUS_KEY_GETSIZE_FAILED      = -3,  /*!< Key get size failed */
        V_STATUS_KEY_INIT_FAILED         = -4,  /*!< Key init failed */
        V_STATUS_VER_GETSIZE_FAILED      = -5,  /*!< Verification get size failed */
        V_STATUS_VER_INIT_FAILED         = -6,  /*!< Verification init failed */
        V_STATUS_VERIFICATION_FAILED     = -7,  /*!< Verification failed */
        V_STATUS_RANGE_SAFE_FAILED       = -8,  /*!< Is Range Safe failed */
        V_STATUS_ERR                     = -9   /*!< Unexpected error */
    };

    CodeVerify *c_verifier = 0;

    unsigned char * ReadFromFile(const string &file_name, unsigned int &fsize;);
    int InitVerification(void *handle, unsigned char *sb, unsigned int sb_size);


Let's add a code, for loading of our dynamic library and the file of the signature

Declaration of variables and reading file
#if defined _WIN32
    string dll_name = "module.dll"; //path to dll.
    string sb_name = "module.sb"; //path to sb.
#else
    string dll_name = "libmodule.so"; //path to shared library.
    string sb_name = "module.sb"; //path to sb.
#endif
    //Read SB to buffer:
    unique_ptr<unsigned char[]> sb;
    unsigned int sb_size = 0;
    sb.reset(ReadFromFile(sb_name, sb_size));
    if(!sb)
    {
        cout << "SB file reading failed!" << endl;
        return V_STATUS_ERR;
    }



We will add the function of reading from files used in a code to the end of the file:

Function of reading from the file
    unsigned char * ReadFromFile(const string &file_name, unsigned int &fsize;)
    {
        ifstream f;
        f.open (file_name, ifstream::in | ifstream::binary);
        if(f)
        {
            f.seekg(0, ios::end);
            unsigned int size = (unsigned int)f.tellg();
            f.seekg(0, ios::beg);
            // allocate memory to contain file data
            unique_ptr<unsigned char[]> res(new unsigned char[size]);
            f.read((char*)res.get(), size);
            if(!f)
            {
                f.close();
                return 0;
            }
            f.close();
            fsize = size;
            return res.release();
        }
        return 0;
    }


Initialization of a context of check of integrity happens in the InitVerification function () which accepts the pointer on the file and data from the file of the signature as input parameters.

Initialization
    //Get DLL handle:
#if defined _WIN32
    HMODULE handle = GetModuleHandle(dll_name.c_str());
#else
    Dl_info dl_info;
    dladdr((void*)sum, &dl;_info);
    void *handle = dlopen(dl_info.dli_fname, RTLD_NOW);
#endif
    if(!handle)
    {
        cout << "Dll handle can't be obtained, dll-name: " << dll_name.c_str() << endl;
        return V_STATUS_ERR;
    }
    int ret = V_STATUS_OK;
    ret = InitVerification(handle, sb.get(), sb_size);
    if(V_STATUS_OK != ret)
    {
        cout << "InitVerification failed!  Error code: " << ret << endl;
#if defined _WIN32
        if(c_verifier) delete [](char*)c_verifier;
#else
        if(handle) dlclose(handle);
        if(c_verifier) delete [](char*)c_verifier;
#endif
        return V_STATUS_ERR;
    }


Let's add InitVerification function implementation () to the end of the file:

InitVerification function ()
int InitVerification(void *handle, unsigned char *sb, unsigned int sb_size)
{
    DECLARE_pubkey_der;
    VerificationKey *m_verifier = 0;
    unsigned int size = 0;
    int err = V_STATUS_ERR;
    if(!handle || !sb)
        return V_STATUS_NULL_PTR;
    if(!sb_size)
        return V_STATUS_BAD_ARG;
    DEFINE_pubkey_der;
    //Get size of VerificationKey context:
    if(VK_STATUS_OK != VerificationKey_GetSize(pubkey_der, sizeof(pubkey_der), &size;))
    {
        return V_STATUS_KEY_GETSIZE_FAILED;
    }
    //VerificationKey context memory allocation:
    m_verifier = (VerificationKey *)(new char[size]);
    //Init VerificationKey context:
    if(VK_STATUS_OK != VerificationKey_Init(m_verifier, pubkey_der, sizeof(pubkey_der)))
    {
        err = V_STATUS_KEY_INIT_FAILED;
        if(m_verifier)  delete [] (char*)m_verifier;
        return err;
    }
    //Get size of CodeVerify context:
    if(CV_STATUS_OK != CodeVerify_GetSize(sb,sb_size,m_verifier,&size;))
    {
        err = V_STATUS_VER_GETSIZE_FAILED;
        if(m_verifier)  delete [] (char*)m_verifier;
        return err;
    }
    //CodeVerify context memory allocation:
    c_verifier = (CodeVerify*)(new unsigned char[size]);
    //Init CodeVerify context:
    if(CV_STATUS_OK != CodeVerify_Init(c_verifier,size,(const void *)handle,sb,sb_size,m_verifier))
    {
        err = V_STATUS_VER_INIT_FAILED;
        if(m_verifier)  delete [] (char*)m_verifier;
        return err;
    }
    err = V_STATUS_OK;
    if(m_verifier)  delete [] (char*)m_verifier;
    return err;
}


After initialization of a context of check of integrity everything is ready to add dynamic check of integrity of the dynamic library loaded into memory. It becomes by means of function call of CodeVerify_Verify (). This function it is possible to cause an unlimited number of times, in different places of a code to check integrity of the loaded library. This function has an input parameter - work_factor by means of which it is possible to reduce the extent of the checked memory of the loaded library. For example, at work_factor = the 3rd complete check of the loaded dynamic library will come to the end for three function calls. The pass_count variable transferred every time at function call of CodeVerify_Verify (), contains the number of complete checks of dynamic library.
If you use global variables and do not want that someone changed them in the course of execution of an application code, it is possible to use the CodeVerify_IsRangeSafe function () to be convinced that integrity of the data interesting you is checked by function call of CodeVerify_Verify ():

Check of a variable and completion of work
    ret = CodeVerify_IsRangeSafe(c_verifier, global_array, sizeof(global_array));
    if(CV_STATUS_OK != ret)
    {
        cout << "IsRangeSafe failed!" << endl;
#if defined _WIN32
        if(c_verifier) delete [](char*)c_verifier;
#else
        if(handle) dlclose(handle);
        if(c_verifier) delete [](char*)c_verifier;
#endif
        return V_STATUS_RANGE_SAFE_FAILED;
    }
    cout << "Range verification was successfully done!" << endl;


After you complete to use functions of codeverify library, do not forget to release the memory used for integrity check and to unload dynamic library from memory:

Release of resources
#if defined _WIN32
        if(c_verifier) delete [](char*)c_verifier;
#else
        if(handle) dlclose(handle);
        if(c_verifier) delete [](char*)c_verifier;
#endif


For assembly of the program with the included code of check of integrity it is possible to use the following command of compilation:

cl sample_app_without_verification.cpp /I../../inc  /link ../../lib/win-x86/codeverify.lib module.lib

If compilation took place successfully and the application was successfully created, it is possible to start it and you have to receive the following:

> sample_app_without_verification
Range verification was successfully done!
sum(3,5) returns 8
sum(3,5) + global_array[3] returns 12


If to try to change a function code of module.dll library, to rebuild it and to try to start with the test application, without recreating the file of the digital signature, check of integrity has to notice to a submeme and give an error message:

> sample_app_without_verification
InitVerification failed! Error code:-6


Here perhaps and everything that it is necessary to make by means of the Intel Tamper Protection Toolkit tools to check integrity of the loaded dynamic library.

I hope this short digression to the Intel Tamper Protection Toolkit tool it was useful and helped you to understand that it is that is able to do and as it can use for protection of the software. Good luck!

* Other names and trademarks are property of the legal owners.

This article is a translation of the original post at habrahabr.ru/post/273183/
If you have any questions regarding the material covered in the article above, please, contact the original author of the post.
If you have any complaints about this article or you want this article to be deleted, please, drop an email here: sysmagazine.com@gmail.com.

We believe that the knowledge, which is available at the most popular Russian IT blog habrahabr.ru, should be accessed by everyone, even though it is poorly translated.
Shared knowledge makes the world better.
Best wishes.

comments powered by Disqus