You are here : Home » Learning security » Operating Systems » Windows » Code segment encryption

Code segment encryption

Implementation of "Rootkit Arsenal" cryptor.

D 2 March 2014     H 19:43     A Emeric Nasi     C 0 messages


agrandir

Note: This document requires base of C programming and Microsoft Windows C programming. Also this document is made by non-expert in the field of windows system programming so I hope experts reading this document will be indulgent, do not hesitate to tell me how to improve my code!
License : Copyright Emeric Nasi, some rights reserved
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
Creative Commons License

I. Introduction.

In the Bill Blundens’ "The Rootkit ARSENAL" Edition 1 book, Mr Blunden describes how malware can evade detection and slow down reverse engineering by using code armoring. I implemented Bills’ cryptor and completed it to add patching of target program.
This article presents the modifications I made to the original code and the different tests I did on segment encryption.
It is not necessary to have the "Rootkit Arsenal" book to understand this article but the functions I did not modify are not in this article and if you need the full source code you can ask me by email or may also want to buy this excellent book:-).

II. Theory

II.1 Memory segmentation

An executable file, and its virtual memory image are made up of several sections. Some of this sections are represented by memory segments. Some segments are used to store data and others to store executable machine code. In this article we present how a program can encrypt one of the segment of another application and how this application can self decrypt its encrypted segment at runtime.

Note : Even if it is possible to merge the segments in a way that the entire application is encrypted (except decryption stub), this article does not describe how packer works. In this article we need access to the source core of target application. It is easier to code and also more felxible since it can be useful to encrypt a small part of an application. The next article about hiding meterpreter shellcode in an executable program and bypassing AntiVirus demonstrates this.

Here is the classical segmentation of a Windows portable executable binary file.

Other segment can appear in your files but these are the most important ones. PE headers is complex and I recommend you find some information about it. In this article we use the PE optional header which contains information about the application segments.

The .textbss segment is empty on the binary file. It is used to reserve room in virtual memory for non initialized global variables

The .text segment contains the executable code of the program

The .rdata segment contains read-only data. It is used to global constants (which includes strings). For example in printf("hello"); "hello" goes in .rdata.

The .data segment contains initialized global variables which are not constants. for example this global variable char var[] =  "var"; is not a constant string, it is an array and goes in .data.

The .rsrc segment is present if you include resources to your file (*.rc files). Resources file can be used to provide information such as company name and application version.

When the executable is loaded in memory, the stack and heap segment are initialized to store local variables and dynamic allocated value. A part from this the segment layout will not change that much as you can see

II.2 Mechanism

What we want to do is modify an application (the target) in such a way that another application (the cryptor) can encrypt one of its segment. We also want the target application to decrypt itself at runtime.
Following the "Rootkit arsenal" book, we are going to create new segments in the target.

  • a .code segment that will be encrypted.
  • a .stub segment that is used to decrypt .code segment

In his book Bill Blunden merged the .text and .data segment into .code before encrypting it. There are a lot of other ways to do it. You can merge everything into one segment. You may want to have .text and .code separated so you don’t encrypt all your executable code. you can also create your own data segment and merge it to .code so that you don’t have to encrypt all the application initialiazed global variables and just hide the data you want.

In this article we are going to create a .code segment that will contain the executable code we want to cipher. To make it simple we are not going to merge another segment to it.

Here is the segment layout we will have in this example

II.3 Tools

Here are the tools you need:

  • a C source code editor
  • a C compiler/linker
  • a debugger
  • dumpbin.exe tool
  • a hex editor

Personally I used Microsoft Visual Studio express 2010 which is free and provides the source code editor, the compiler, the linker, the debugger (with a very practical code machine view), and dumpbin.exe is provided by the Visual Studio shell.
I recommend next to Visual Studio you open the Visual studio CMD shell (can be found in startup/program menus). You can easily get PE file segments information and verify your values are ok using:
c:\test>dumpbin /HEADERS file.exe

Example of output:

For the hex editor I used the free edition of Cygnus Hex Editor.

III. Adapt target software

III.1 Create .code segment

It is very simple to create a new segment using #pragma section.
You can then specify in which segment your information goes. There are 4 types of information in segments: executable code (pushed with #pragma code_seg), initialized data (pushed with #pragma data_seg), uninitialized data (pushed with #pragma bss_seg) and constant variables (pushed with #pragma const_seg
).
Remember that a code segment will not store global variables or strings.

In out example, we want to put executable code of a C file in .code segment.

  1. /* Your system includes */
  2. #include <windows.h>
  3.  
  4. /* Declare .code as a read/write/execute segment */
  5. #pragma section(".code",execute, read, write)
  6. #pragma comment(linker,"/SECTION:.code,ERW")
  7.  
  8. /* .code segment begin (all generated executable code below will go in .code segment ) */
  9. #pragma code_seg(".code")

III.2 Create decryption stub

Why do we need a .stub segment since we do not encrypt all the code? Well we need the cryptor to be able to patch the target self-decryption routines and the code we want to patch will be easier to locate in the .stub section. Also variants of this article can be used to merge all segments into one (.code). So having .stub section is more generic.

  1.  
  2. // .stub SECTION
  3. #pragma section(".stub", execute, read, write)
  4. #pragma code_seg(".stub")
  5.  
  6. #define CODE_BASE_ADDRESS 0x15151515 // dumpbin file pointer to raw data (do not change, this will be patched by cryptor)
  7. #define CODE_SIZE 0x14141414 // dumpbin size of Virtual memory (do not change, this will be patched by cryptor)
  8.  
  9. /* Decrypt .code block encrypted by cryptor */
  10. /* In this function do not declare array to avoid security cookies checks (see http://msdn.microsoft.com/en-us/library/8dbf701c.aspx) */
  11. /* Or disable security check (GS- option) on prog compilation */
  12. void decryptCodeSection()
  13. {
  14. unsigned char *ptr;
  15. long int i;
  16. long int nbytes;
  17. int cpt = 0;
  18. BYTE key[] = { '1','2','3','4','5','6','7','8','\0'};/* Note: Avoid strings if you plan to encryt .rdata segment */
  19. int keyLength = 8;
  20. ptr = (unsigned char *)CODE_BASE_ADDRESS;/* This will be patched by cryptor*/
  21. nbytes = CODE_SIZE;/* This will be patched by cryptor*/
  22.  
  23. // decrypt code segment
  24. for( i = 0 ; i < nbytes ; i++ )
  25. {
  26. ptr[i]=ptr[i]^key[cpt];
  27. cpt = cpt + 1;
  28. if(cpt == keyLength)
  29. cpt = 0;
  30. }
  31. return;
  32. }
  33.  
  34. /* Program first entry function */
  35. int main()
  36. {
  37. decryptCodeSection();
  38. realmain(); /* Call decrypted program entry point */
  39. return 0;
  40. }

 

III.4 Compilation and link options

Because the target will self-modify itself, we must avoid security cookies (used for stack verification). For that we need to remove security check. The next compilation options are thus mandatory:

/GS-  -> Disable functions stack verification relying on secure cookies

Another option is almost mandatory, statically include the Runtime Library. If the Microsoft Runtime Library is not included in the target, this code will work but you will face portability issues.
Use one of the next two Runtime Library options.

/MTD -> for debug
/MT -> for release

To avoid complications we want to have fix address and remove data execution prevention. For that we have to use the next options for linker.

/DYNAMICBASE:NO
/FIXED
/NXCOMPAT:NO

You may also be interested into removing debugging informations but it is not mandatory for this code to work (and very useful to debug when it doesn’t work!).

III.5 Other possibilities

Other segments layouts are possible, and will be described in future articles.
For example, you can encrypt data and constant in your code by creating a custom data segment.

  1. // create a data segment
  2. #pragma section(".codedata", read, write)
  3. // create the code section (to be decrypted)
  4. #pragma section(".code",execute, read, write)
  5. // Merge .codedata into .code (which will be encrypted by cryptor)
  6. #pragma comment(linker,"/MERGE:.codedata=.code")

Another possibility is to merge .text, .data, .rdata segments into .code which results in the next memory layout:

Note that in this case, the runtime library is encrypted. That means you need to declare a new entry point for the target, example:

  1. // .stub SECTION
  2. #pragma section(".stub", execute, read, write)
  3. #pragma comment(linker,"/entry:\"ProgEntry\"") // new entry point
  4. #pragma code_seg(".stub")
  5.  
  6. // decryption routine
  7.  
  8. /* Entry point function*/
  9. int ProgEntry()
  10. {
  11. decryptCodeSection();
  12. mainCRTStartup(); /* Call runtime library entry point, which will then call main */
  13. return 0;
  14. }

There is however a drawback to this method. Because of the strange PE header and the encryption of most of the file, some AntiVirus may consider the result of this method to be a malware.

IV. Build Cryptor

IV.1 Retrieve wanted information

The role of the cryptor is to encrypt the target programs’ .code segment as well as patching the.stub segment so that the target is able to self-decrypt when launched.
For that, the cryptor will browse through the binary file generated in the previous section and will find the address and size of .code and .stub segment. We need both file offset (to modify the binary target) and virtual memory address (to indicate to target program where is the segment he should decrypt at run-time).

First you need to map the file in memory using CreateFileMapping and MapViewOfFile. I did not do any functional modifications on this code. It can be found in the book or on the Internet.
Once this is done we parse the mapped file to fetch section headers informations.

  1. /**
  2.  * Get information of .code and .stub segments
  3.  */
  4. void getSegmentsInfo(LPVOID baseAddress, SEGMENT_INFO_PTR codeSegmentInfo,SEGMENT_INFO_PTR stubSegmentInfo)
  5. {
  6. PIMAGE_DOS_HEADER dosHeader;
  7. PIMAGE_NT_HEADERS peHeader;
  8. IMAGE_OPTIONAL_HEADER32 optionalHeader;
  9.  
  10. dosHeader = (PIMAGE_DOS_HEADER)baseAddress;
  11. if(((*dosHeader).e_magic)!=IMAGE_DOS_SIGNATURE)
  12. {
  13. printf("getSegmentsInfo: Dos signature not matched\n");
  14. return;
  15. }
  16. printf("getSegmentsInfo: Dos signature=%X\n",(*dosHeader).e_magic);
  17.  
  18. peHeader = (PIMAGE_NT_HEADERS)((DWORD)baseAddress+(*dosHeader).e_lfanew);
  19. if(((*peHeader).Signature)!=IMAGE_NT_SIGNATURE)
  20. {
  21. printf("getSegmentsInfo: PE signature not matched\n");
  22. return;
  23. }
  24. printf("getSegmentsInfo: PE signature=%X\n",(*peHeader).Signature);
  25.  
  26. optionalHeader = (*peHeader).OptionalHeader;
  27. if((optionalHeader.Magic)!=0x10B)
  28. {
  29. printf("getSegmentsInfo: Optional header magic number does not match\n");
  30. return;
  31. }
  32. printf("getSegmentsInfo: OPtional header magic nb=%X\n",optionalHeader.Magic);
  33.  
  34. (*codeSegmentInfo).moduleBase = optionalHeader.ImageBase;
  35. (*stubSegmentInfo).moduleBase = optionalHeader.ImageBase;
  36.  
  37. printf("getSegmentsInfo: # sections=%d\n",(*peHeader).FileHeader.NumberOfSections);
  38.  
  39. /* Fill code information with content of code segment */
  40. TraverseSectionHeaders(IMAGE_FIRST_SECTION(peHeader),(*peHeader).FileHeader.NumberOfSections,codeSegmentInfo,".code");
  41. TraverseSectionHeaders(IMAGE_FIRST_SECTION(peHeader),(*peHeader).FileHeader.NumberOfSections,stubSegmentInfo,".stub");
  42.  
  43. return;
  44. }

The next function is used get the next information for any section:

  • The segment offset in raw file
  • The file segment size
  • The virtual memory offset of the segment
  1. /**
  2.  * Look for sectionName segment in mapped file
  3.  * addrInfo will be filled with the segment information
  4.  */
  5. void TraverseSectionHeaders
  6. (
  7. PIMAGE_SECTION_HEADER section,
  8. DWORD nSections,
  9. SEGMENT_INFO_PTR addrInfo,
  10. char * sectionName
  11. )
  12. {
  13. DWORD i ;
  14. /* Copy pointer to initial section (so this function can be called several times) */
  15. PIMAGE_SECTION_HEADER localSection = section;
  16. printf("\n\nTraverseSectionHeaders: searching for segment in section headers\n");
  17. for(i=0;i<nSections;i++)
  18. {
  19. printf(" ==================== \n");
  20. printf("\tName: %s\n",(*section).Name);
  21. if(strcmp((*section).Name,sectionName)==0)
  22. {
  23. (*addrInfo).fileSegmentOffset = (*section).PointerToRawData; /* Location of segment in binary file*/
  24. (*addrInfo).fileSegmentSize = (*section).SizeOfRawData; /* Size of segment */
  25. (*addrInfo).memorySegmentOffset = (*section).VirtualAddress; /* Offset of segment in memory at runtime */
  26. }
  27. section = section+ 1;
  28. }
  29. return;
  30. }

IV.2 Encrypt target segment

Now that we have the size and location of .code segment in binary file, we can open the file and encrypt the wanted bytes.
This code is almost the same as provided by Blunden except the adaptation for the multiple bytes XOR encryption (witch is not much!). The code is not really optimized but very practical for debugging purpose. In this function we:

  • Open the binary target file
  • Seek .code segment
  • Load .code segment in buffer
  • Encrypt buffer
  • Write encrypted buffer in place of clear-text .code segment
  • Close file and leave
  1. /**
  2.  * Encrypt .code segment bytes in the given file
  3.  */
  4. void cipherBytes(char* fileName, SEGMENT_INFO_PTR addrInfo)
  5. {
  6. DWORD fileOffset;
  7. DWORD nbytes;
  8.  
  9. FILE* fptr;
  10. BYTE *buffer;
  11. DWORD nItems;
  12. DWORD i;
  13. BYTE key[] = "ab345izz";
  14. int keyLength = 8;
  15. int cpt = 0;
  16.  
  17. fileOffset = addrInfo->fileSegmentOffset;
  18. nbytes = addrInfo->fileSegmentSize;
  19. /* Allocate memory in buffer that will store content of segment */
  20. buffer = (BYTE*)malloc(nbytes);
  21. if(buffer == NULL)
  22. {
  23. printf("cipherBytes: malloc error \n");
  24. return;
  25. }
  26.  
  27. /* Open binary file */
  28. fptr = fopen(fileName,"r+b");
  29. if(fptr == NULL)
  30. {
  31. printf("cipherBytes: fopen error \n");
  32. return;
  33. }
  34. /* Seek .code section using calculated offset and copy content into buffer*/
  35. if(fseek(fptr, fileOffset, SEEK_SET)!=0)
  36. {
  37. printf("cipherBytes: Unable to set file pointer to %ld \n", fileOffset);
  38. fclose(fptr);
  39. return;
  40. }
  41. nItems = fread(buffer, sizeof(BYTE), nbytes, fptr);
  42. if(nItems <nbytes)
  43. {
  44. printf("cipherBytes: Trouble reading nItems = %d \n",nItems);
  45. fclose(fptr);
  46. return;
  47. }
  48.  
  49. /* Encrypt buffer */
  50. for( i = 0 ; i < nbytes ; i++ )
  51. {
  52. buffer[i]=buffer[i]^key[cpt];
  53. cpt = cpt + 1;
  54. if(cpt == keyLength)
  55. cpt = 0;
  56. }
  57.  
  58. /* Replace current .code section in file by encrypted one */
  59. if(fseek(fptr, fileOffset, SEEK_SET)!=0)
  60. {
  61. printf("cipherBytes: Unable to set file pointer to %ld \n", fileOffset);
  62. fclose(fptr);
  63. return;
  64. }
  65. nItems = fwrite(buffer, sizeof(BYTE), nbytes, fptr);
  66. if(nItems <nbytes)
  67. {
  68. printf("cipherBytes: Trouble writing nItems = %d \n",nItems);
  69. fclose(fptr);
  70. return;
  71. }
  72.  
  73. printf("Successfully ciphered %d bytes\n",nbytes);
  74. fclose(fptr);
  75. return;
  76. }

IV.3 Patch target .stub section

After having encrypted the .code section, we need to patch the .stub section so that the target can decrypt itself. In this function we:

  • Open the binary target file
  • Seek .stub segment
  • Load .stub segment in buffer
  • locate CODE_BASE_ADDRESS and CODE_SIZE
  • Replace values by virtual memory offset and size of .code section
  • Write patched buffer in place of .stub segment
  • Close file and leave
  1. /**
  2.  * Patch the filepath file (the .stub segment)
  3.  * Here we replace CODE_BASE_ADDRESS and CODE_SIZE by newBaseAddr and newSegSize
  4.  * newBaseAddr is the Virtual memory base address of .code segment in target file
  5.  * newSegSize contains the size of the target file .code segment
  6.  */
  7. void patchStub(char * filepath, SEGMENT_INFO_PTR addrInfo, DWORD newBaseAddr, DWORD newSegSize )
  8. {
  9. DWORD fileOffset;
  10. DWORD nbytes;
  11. DWORD nItems;
  12. /* Signature to locate where segment memory base address should be written */
  13. BYTE baseAddrSignature[] = { 0x15, 0x15, 0x15, 0x15, 0x00 };
  14. /* Signature to locate where segment size should be written*/
  15. BYTE segSizeSignature[] = { 0x14, 0x14, 0x14, 0x14, 0x00 };
  16. BYTE * baseAddrAddress = NULL;
  17. BYTE * segSizeAddress = NULL;
  18. BYTE *buffer;
  19. FILE* fptr;
  20. fileOffset = addrInfo->fileSegmentOffset;
  21. nbytes = addrInfo->fileSegmentSize;
  22.  
  23. /* Allocate memory in buffer that will store content of segment */
  24. buffer = (BYTE*)malloc(nbytes);
  25. if(buffer == NULL)
  26. {
  27. printf("patchStub: malloc error \n");
  28. return;
  29. }
  30.  
  31. /* Open binary file */
  32. fptr = fopen(filepath, "r+b");
  33. if(fptr == NULL)
  34. {
  35. printf("patchStub: fopen error \n");
  36. return;
  37. }
  38.  
  39. /* Seek .stub section using calculated offset*/
  40. if(fseek(fptr, addrInfo->fileSegmentOffset, SEEK_SET)!=0)
  41. {
  42. printf("patchStub: Unable to set file pointer to %ld \n", addrInfo->fileSegmentOffset);
  43. fclose(fptr);
  44. return;
  45. }
  46. /* Copy content of stub segment into buffer */
  47. nItems = fread(buffer, sizeof(BYTE), nbytes, fptr);
  48. if(nItems <nbytes)
  49. {
  50. printf("patchStub: Trouble reading nItems = %d \n",nItems);
  51. fclose(fptr);
  52. return;
  53. }
  54.  
  55. /* Search the baseAddress in buffer section */
  56. baseAddrAddress = binStrstr(buffer,baseAddrSignature);
  57. /* Change base Address by calculated value */
  58. memcpy(baseAddrAddress,&newBaseAddr,sizeof(newBaseAddr));
  59.  
  60. /* Search the baseAddress in buffer section */
  61. segSizeAddress = binStrstr(buffer,segSizeSignature);
  62. /* Change base Address by calculated value */
  63. memcpy(segSizeAddress,&newSegSize,sizeof(newSegSize));
  64.  
  65. /* Replace current .stub section in file by patched one */
  66. if(fseek(fptr, fileOffset, SEEK_SET)!=0)
  67. {
  68. printf("patchStub: Unable to set file pointer to %ld \n", fileOffset);
  69. fclose(fptr);
  70. return;
  71. }
  72. nItems = fwrite(buffer, sizeof(BYTE), nbytes, fptr);
  73. if(nItems <nbytes)
  74. {
  75. printf("patchStub: Trouble writing nItems = %d \n",nItems);
  76. fclose(fptr);
  77. return;
  78. }
  79. printf("Successfully patched file\n");
  80. fclose(fptr);
  81.  
  82. return ;
  83. }

IV.4 Main function

  1. /**
  2.  * Cryptor entry point
  3.  */
  4. void main(int argc, char *argv[])
  5. {
  6. char *fileName;
  7. HANDLE hFile;
  8. HANDLE hFileMapping;
  9. LPVOID fileBaseAddress;
  10. BOOL retVal;
  11. /* To store information of .code and .stub segments */
  12. SEGMENT_INFO codeSegmentInfo;
  13. SEGMENT_INFO stubSegmentInfo;
  14.  
  15. if(argc <2)
  16. {
  17. printf("main: Not enough arguments");
  18. return;
  19. }
  20. fileName = argv[1];
  21. /* Map target file */
  22. retVal = getHMODULE(fileName, &hFile, &hFileMapping, &fileBaseAddress);
  23. if(retVal==FALSE)
  24. {
  25. return;
  26. }
  27.  
  28. /* Init structures */
  29. codeSegmentInfo.moduleBase = (DWORD)NULL;
  30. codeSegmentInfo.memorySegmentOffset = (DWORD)NULL;
  31. codeSegmentInfo.fileSegmentOffset = (DWORD)NULL;
  32. codeSegmentInfo.fileSegmentSize = (DWORD)NULL;
  33. stubSegmentInfo.moduleBase = (DWORD)NULL;
  34. stubSegmentInfo.memorySegmentOffset = (DWORD)NULL;
  35. stubSegmentInfo.fileSegmentOffset = (DWORD)NULL;
  36. stubSegmentInfo.fileSegmentSize = (DWORD)NULL;
  37.  
  38. /* Fill segments information */
  39. getSegmentsInfo(fileBaseAddress,&codeSegmentInfo,&stubSegmentInfo);
  40.  
  41. printf("\n\n=======================\n");
  42. printf(".code segment information: \n");
  43. printf("RAM image base =0x%08X\n",codeSegmentInfo.moduleBase);
  44. printf("RAM segment offset =0x%08X\n",codeSegmentInfo.memorySegmentOffset);
  45. printf("File offset of code =0x%08X\n",codeSegmentInfo.fileSegmentOffset);
  46. printf("File size of code =0x%08X\n",codeSegmentInfo.fileSegmentSize);
  47. printf("\n\n=======================\n");
  48. printf(".stub segment information: \n");
  49. printf("RAM image base =0x%08X\n",stubSegmentInfo.moduleBase);
  50. printf("RAM segment offset =0x%08X\n",stubSegmentInfo.memorySegmentOffset);
  51. printf("File offset of code =0x%08X\n",stubSegmentInfo.fileSegmentOffset);
  52. printf("File size of code =0x%08X\n",stubSegmentInfo.fileSegmentSize);
  53. closeHandles(hFile, hFileMapping,fileBaseAddress);
  54. cipherBytes(fileName,&codeSegmentInfo);
  55.  
  56. patchStub(fileName,&stubSegmentInfo,codeSegmentInfo.moduleBase+codeSegmentInfo.memorySegmentOffset,codeSegmentInfo.fileSegmentSize);
  57.  
  58. return;
  59. }
Any message or comments?
pre-moderation

This forum is moderated before publication: your contribution will only appear after being validated by an administrator.

Who are you?
Your post