Creating polymorphic executables from c object files

Here I have created a walkthrough on how to create polymorphic executables from C source code. This requires the use of the objconv command to convert c object files to NASM. With the NASM assembly language file you must insert the the decryption function and specify the beginning and end of the locations you want encrypted. The objconv command covered in the examples section does not completely convert the object file into an assembly language compatible with the nasm command. The directions text file covers the modifications you must make and the gcc flags used to compile the object file generated from the nasm command. This can only be done on a UNIX based operating system and requires a working version of nasm, objconv, gcc and mkpoly. Mkpoly includes a simple example however I have more advanced examples under the polymorphic tab of what your nasm assembly language file should look like before you convert it back to an object file.

Walk-Through

Here is a step by step walkt-through on how to create a polymorphic executable from c source code

  1. Make sure you have all the tools

    Before you start you must build the mkpoly utility and acquire a working version of gcc, nasm, and objconv. Also obtain a c source code file of your choosing or use my example.

  2. Compile your your c code without linking to obtain your object file/files

    gcc -c example.c -o example.o

  3. Use the objconv command to convert the object file into NASM assembly language

    objconv -fnasm example.o

    This will create a file with .asm appended to the name. Here is an example of what the asm file should look like before the modifications.

  4. Make modifications to the file so it is compatible with the nasm command

    • Remove the function statements that are after the global statements at the beginning of the file

      The global statements that are functions at the beginning of the file should have the ': function' part removed. Here is an example of what that change should look like

    • Remove the section align execute stamentes and the entire .eh_frame section

      Each section has either 'align = # excecute' or 'align = # noexecute'. Remove these statements after every section as well as the entire '.eh_frame' section used for debugging. The nasm command cannot correctly compile this section. Here is an example of what that modification should look like.

    • Relocate the global variables

      The global variable located in the global statements at the beginning of the file should be removed and relocated to the '.bss' section for reserved memory. NASM doesn't initialize global variables in the way objconv converts them. The notation in the '.bss' section should be: variable_name: resb #ofbytes. For example a pointer should be 8 bytes and int should be 4. Here is an example of how that should look.

  5. Insert your decrypter section and target decryption function

    mkpoly function includes a file called mkpoly.inc. This file should be included with the statement '%include "mkpoly.inc"' inserted at the beginning of the asm file. Make sure this file is in your directory when the nasm command is used to compile your asm file. The purpose of this file is to insert the polymorphic decryption function and preallocate space with 'nop' instructions that the mkpoly command will overwrite with the decryption code. When the nasm command compiles the .asm with the mkpoly.inc file included, nasm will find the statement called DECRYPTOR_SECTION and insert the necessary instructions for the decryption function. The DECRYPTOR_SECTION takes two arguments. The first argument is the start of target area that you want to be encrypted and the second argument is the length of the target area. To specify these locations use two symbols that specify the start and end of the target area you want to decrypt. Place the start and end variables before and after the target section you want to be decrypted. The decryption function decrypts 16 bytes at a time so this section should be aligned with 16 bytes by inserting the statement 'align 16' at the beginning and end of your target section. Create a decrypt function where you should insert your DECRYPTOR_SECTION. To specify the start location of the decrypted section, the first argument should be the symbol that is located at the start of the target decryption section. To specify the size of the decryption section subtract the end symbol by the start symbol. The contents inserted at the DECRYPTOR_SECTION will change the contents of your rsi register so push the rsi register before the statement and pop it after. Insert the call to the decrypt function wherever you want but make sure that that it is not in the encrypted area and no encrypted functions are called before the decryption function. Here is an example of the NASM code for my example. The symbols used for the beginning and end of the target decrypted section are ?start and ?end. Here is an example of multilayered polymorphism that utilizes dual decryption functions with two sets of start and end variables.

  6. Recompile the object file with nasm

    nasm -felf64 example.asm

    This command will create an object file with the same name as the executable. Make sure the mkpoy.inc file is in the same directory.

  7. Link the executable with gcc

    Compiling object files with nasm does not support position independent executables that most Linux distros use for dynamic linking. To get around this there are two options to avoid segmentation fault. One option is the -no-pie flag which still allows standard dynamic linkage. The other option is the -static flag which will use no dynamic linkage (note this will create a very large executable if many functions are called from shared libraries).

  8. Find the hex addresses

    Here is an example of how you should find the decryption function and start/end addresses. These are important addresses that you will need for the next step. The address you want for the decryption function is decrypt.mkpoly_func.

  9. Use the mkpoly function to convert the executable to polymorphic

    mkpoly example offset_of_decrypt_section decrypt_section_size offset_of_decrypt.mkpoly_func

    The offset of the decrypt section should be the hex address of the start symbol excluding the most significant digit. This would be 'a20' for the example above. The decrypt section size will be the address of the end symbol minus the start symbol. Use a hexidecimal calculator for to find this number and make sure the result is in hexidecimal and not decimal notation (here is a hexidecimal calculator). The final argument is the address of decrypt.mkpoly_func excluding the most significant digit as well. This would be '1135' from the example above. I am not sure why the most significant digit is excluded. This will return a executable appended with '.crypt'. The permissions of the .crypt executable will have to be modified to run the executable.

  10. Additional obfuscation to escape reverse-engineering

    To stip the executable of all symbols use "strip --strip-all executable" this will remove all function symbols including the ones used to find the addresses earlier.

    You can also inject junk code by pushing to a register, doing random operations to that register, and popping the register back. These techniques will cause even more obfuscation. The Mkpoly function can be used in several different ways. You can make muliple layers of decryption or decrypt individual functions before they are called. When using layered decryption, it is important to always decrypt the outer-most layer first. Also note that the mkpoly function should be used to encrypt layered sections in the opposite order that they are decrypted (inner section to outer section).

  11. How it works

    Mkpoly encrypts the target code while creating a corresponding decryption loop that takes in 16 bytes of code at a time loaded into two registers. Decryption operations are executed on the registers and the contents are written back to memory with the correct instruction decrypted. Mkpoly generates random encryption with an inverse decryption sequence.