This article was written to provide the better understanding to people who do not have any experience in this field. I am not going to describe about PE structure, so I think it was explained enough in Matt Pietrek's articles in MSDN. I strongly recommend to read his article before continuing to read this article if you don't have any previous experience with PE structure. You could find useful the relevant reference link at the end of article.
This article will appear in three parts:
This article contains yoda's protector source from version 1.01 to 1.03. It is first time people can look at its source after six month of appearance on web. It is based on [6] yoda's Crypter assembly source by Danilo Bzdok and compression source from [7] UPX library by Markus F.X.J. Oberhumer & Laszlo Molnar and [9]aPLib compression library by Joergen Ibsen. Thus, we should appreciate them for helping me to create this tool. I also should be grateful to people for trying and testing it on different windows version around the world. I think this article will be a small present to all people who involve and assist to make yoda's protector.
The Portable Executable format is standard format under Microsoft Windows NT® operating system. It contains information for code, data, resource, dynamic link libraries importation. It is modifiable by using recent powerful debuggers such as OllyDbg or SoftICE with a little knowledge about assembly language. It causes to waste time of software development companies to obtain money for their productions. Therefore, they are led to purchase the tool like EXE protector to prevent from illegal copy. My idea is what will happen if every person has owner EXE protector. Cracker person will face different EXE protector with different methods. Thus, I think every of us can have their owner EXE protector.
The Portable Executable includes information for the MS-DOS, the Windows NT, and Sections. This information is provided for Windows Operating System to allocate memory, import dynamic link libraries, and perform code.
Table-1
MS-DOS information | IMAGE_DOS_HEADER |
MS-DOS Stub Program | |
Windows NT information | PE Signature ("PE") |
IMAGE_FILE_HEADER | |
IMAGE_OPTIONAL_HEADER | |
Sections information | IMAGE_SECTION_HEADER[0] |
SECTION[0] ... SECTION[n] |
You can find more about PE file format in [1] "Microsoft Portable Executable and Common Object File Format Specification". Matt Pietrek clarifies it enough in [2] "Peering Inside the PE: A Tour of the Win32 Portable Executable File Format", and [3a/b] "An In-Depth Look into the Win32 Portable Executable File Format".
Moreover, PEView [4] by Wayne J. Radburn will help you to find all aspects about PE file format.
There are all requirement data structures for PE file format in included <winnt.h> file inside your Visual C++. IMAGE_DOS_HEADER, IMAGE_NT_HEADERS, IMAGE_SECTION_HEADER structures represent all you need to work with PE file format. The relevant information for these structures could be found in [5] MSDN library.
We have to load PE file format to memory for working with its information. Some Windows API function will help us do it very easy: ''CreateFile(), GetFileSize(), GlobalAlloc(), ReadFile(), CloseHandle()''.
I make a class to work with PE files. It helps me to open files and put DOS header, NT headers, Section Headers, and Sections into separate places in memory and then rebuild all as form as new PE files.
1 class PEStructure 2 { 3 private: 4 DWORD ReservedHeaderSize; 5 DWORD ReservedHeaderRO; 6 public: 7 DWORD dwRO_first_section; 8 IMAGE_DOS_HEADER image_dos_header; 9 char *reservedheader; 10 IMAGE_NT_HEADERS image_nt_headers; 11 IMAGE_SECTION_HEADER image_section_header[MAX_SECTION_NUM]; 12 char *image_section[MAX_SECTION_NUM]; 13 void OpenFileName(char* FileName); 14 void UpdateHeaders(BOOL bSaveAndValidate); 15 void UpdateHeadersSections(BOOL bSaveAndValidate); 16 void Free(); 17 };
''OpenFileName()'' will open PE files to place in ''image_dos_header, reservedheader, image_nt_headers, image_section_header[]'', and ''image_section[]''. All PE structures will abstract by this function in my tool.
1 void PEStructure::OpenFileName(char* FileName) 2 { 3 hFile=CreateFile(FileName, 4 GENERIC_READ, 5 FILE_SHARE_WRITE | FILE_SHARE_READ, 6 NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); 7 if(hFile==INVALID_HANDLE_VALUE) 8 { 9 ShowErr(FileErr); 10 return; 11 } 12 dwFsize=GetFileSize(hFile,0); 13 if(dwFsize == 0) 14 { 15 CloseHandle(hFile); 16 ShowErr(FsizeErr); 17 return; 18 } 19 dwOutPutSize=dwFsize+IT_SIZE+DEPACKER_CODE_SIZE+ALIGN_CORRECTION; 20 pMem=(char*)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT,dwOutPutSize); 21 if(pMem == NULL) 22 { 23 CloseHandle(hFile); 24 ShowErr(MemErr); 25 return; 26 } 27 ReadFile(hFile,pMem,dwFsize,&dwBytesRead,NULL); 28 CloseHandle(hFile); 29 CopyMemory(&image_dos_header,pMem,sizeof(IMAGE_DOS_HEADER)); 30 ReservedHeaderRO=sizeof(IMAGE_DOS_HEADER); 31 ReservedHeaderSize=image_dos_header.e_lfanew-sizeof(IMAGE_DOS_HEADER); 32 reservedheader=new TCHAR[ReservedHeaderSize]; 33 CopyMemory(&image_nt_headers, 34 pMem+image_dos_header.e_lfanew, 35 sizeof(IMAGE_NT_HEADERS)); 36 dwRO_first_section = image_dos_header.e_lfanew + sizeof(IMAGE_NT_HEADERS); 37 UpdateHeadersSections(TRUE); 38 }
It is important to verify file is a Win32 Portable Executable file by checking ''e_magic'' of ''image_dos_header'' and Signature of ''image_nt_header'' to prevent of unpredictable fault.
1 if(PEfile.image_dos_header.e_magic!='ZM') 2 { 3 GlobalFree(pMem); 4 CloseHandle(hFile); 5 if(MakeBackup) DeleteFile(szFnameBackup); 6 ShowErr(PEErr); 7 return; 8 } 9 if(PEfile.image_nt_headers.Signature!='EP') 10 { 11 GlobalFree(pMem); 12 CloseHandle(hFile); 13 if(MakeBackup) DeleteFile(szFnameBackup); 14 ShowErr(PEErr); 15 return; 16 }
There is a trick inside yoda's Protector to create extra section for protection and unpacking purposes. Visual C++ aids it to make this extra part without using of assembler compiler and linker. If you look at PE_LOADER_CODE() subroutine inside the ''CryptStuff.cpp'' you will find what I am talking about. This is same as the method that was done by Danilo Bzdok in his [6] yoda's Crypter. Of course, he did it only by using assembly language in MASM32 without any high level language. I made ''GetFunctionRVA(), GetFunctionSize()'' and CopyFunction()'' to rob code from PE_LOAD_CODE() and use it to create additional section for target PE file.
1 DWORD GetFunctionRVA(void* FuncName) 2 { 3 void *_tempFuncName=FuncName; 4 char *ptempFuncName=PCHAR(_tempFuncName); 5 DWORD _jmpdwRVA,dwRVA; 6 CopyMemory(&_jmpdwRVA,ptempFuncName+1,4); 7 dwRVA=DWORD(ptempFuncName)+_jmpdwRVA+5; 8 return(dwRVA); 9 } 10 11 12 DWORD GetFunctionSize(void* FuncName) 13 { 14 DWORD dwRVA=GetFunctionRVA(FuncName); 15 char* pFuncBody=PCHAR(dwRVA); 16 UCHAR _temp; 17 bool notEnd=TRUE; 18 char *DepackerCodeEnd=new TCHAR[10]; 19 DWORD l=0; 20 do 21 { 22 CopyMemory(&_temp,pFuncBody+l,1); 23 if(_temp==0xC3) 24 { 25 CopyMemory(DepackerCodeEnd,pFuncBody+l+0x01,10); 26 DepackerCodeEnd[9]=0x00; 27 if(strcmp(DepackerCodeEnd,"ETGXZKATZ")==0) 28 { 29 notEnd=FALSE; 30 } 31 } 32 l++; 33 }while(notEnd); 34 return(l); 35 }
''GetFunctionRVA()'' will seek to Relative Virtual Address of specific subroutine to use by ''GetFunctionSize()'' and ''CopyFunction()''. ''GetFunctionSize()'' will return the size of target routine to be exploited by CopyFunction(). It seeks a key word ("ETGXZKATZ") to calculate routine size. Finally, ''CopyFunction()'' is a complete routine to steal all code from PE_LOADER_CODE() subroutine to place in packed PE file.
1 char* CopyFunction(void* FuncName) 2 { 3 DWORD dwRVA=GetFunctionRVA(FuncName); 4 DWORD dwSize=GetFunctionSize(FuncName); 5 char* pFuncBody=PCHAR(dwRVA); 6 char* filebuff=new TCHAR[dwSize+1]; 7 CopyMemory(filebuff,pFuncBody,dwSize); 8 return(filebuff); 9 }
This method can illuminate in the following code:
1 char *pDepackerCode; 2 DWORD DEPACKER_CODE_SIZE; 3 : 4 DEPACKER_CODE_SIZE=GetFunctionSize(PE_LOADER_CODE); 5 pDepackerCode=new TCHAR[DEPACKER_CODE_SIZE]; 6 pDepackerCode=CopyFunction(PE_LOADER_CODE); 7 : 8 void PE_LOADER_CODE() 9 { 10 _asm 11 { 12 //-------------------------------------------------- 13 //---------- START OF THE PE LOADER CODE ----------- 14 DepackerCode: 15 : 16 : 17 : 18 DepackerCodeEND: 19 RET 20 //"ETGXZKATZ" <<-- KEY WORD 21 INC EBP //'E' 22 PUSH ESP //'T' 23 INC EDI //'G' 24 POP EAX //'X' 25 POP EDX //'Z' 26 DEC EBX //'K' 27 INC ECX //'A' 28 PUSH ESP //'T' 29 POP EDX //'Z' 30 } 31 }
UPX compressor source [7] code is an alternative choice to pack sections of PE files. I use [8] LZO data compression library by Markus F.X.J. Oberhumer to pack code and data section. Polymorphism encryption and decryption method [6] by Danilo Bzdok is simple and good enough to crypt PE section by some modification in C++ language as you see inside ''EncryptBuff()'' and ''DecryptBuff()'' in ''PER.CPP''.
This protector separates Sections in different allocation parts of memory. Afterwards it packs and crypts part of sections by CompressPE() and CryptPE().
1 //------ ENCRYPT THE SECTIONS ----- 2 // generate PER 3 PEfile.UpdateHeadersSections(TRUE); 4 SecEncryptBuff=new TCHAR[SEC_PER_SIZE]; 5 SecDecryptBuff=new TCHAR[SEC_PER_SIZE]; 6 MakePER(SecEncryptBuff,SecDecryptBuff,SEC_PER_SIZE); 7 CopyMemory(pDepackerCode+dwRO_SEC_DECRYPT, 8 SecDecryptBuff, 9 SEC_PER_SIZE); 10 // encrypt ! 11 CompressPE(pMem); 12 CryptPE(pMem); 13 RemoveSectionNames(pMem); 14 newsection.Misc.VirtualSize=DepackCodeVirtualSize+0x2000; 15 PEfile.image_section_header[PEfile.image_nt_headers.FileHeader.NumberOfSections-1] 16 .Misc.VirtualSize = newsection.Misc.VirtualSize; 17 PEfile.UpdateHeadersSections(FALSE); 18 //---------------------------------
LZO data compression library [8] has compressor source in C++ and also decompressor source in both C++ and assembly. Hence, we have all sources to pack in high level language and unpack in low level language. I used ''lzo1x_999_compress_level()'' from LZO library to compress sections inside ''CompressPE()'' and its assembly source ''lzo1f_decompress_asm_fast_safe()'' to decompress sections ''inside _DecompressPE()'' in assembly source of ''PE_LOADER_CODE()''.
It is important to pay attention to image_nt_headers.OptionalHeader.SectionAlignment and image_nt_headers.OptionalHeader.FileAlignment to prevent of Win32 incompatible file error in Windows version above Windows 98. Variables in IMAGE_SECTION_HEADER should be adapted to file-alignment and section- alignment. I reserved ''UpdateHeadersSections()'' function to retrieve and rebuilt all PE file format structures.
1 void PEStructure::UpdateHeadersSections(BOOL bSaveAndValidate) 2 { 3 DWORD i; 4 if(bSaveAndValidate)//TRUE = data is being retrieved 5 { 6 DWORD SectionNum = PEfile.image_nt_headers 7 .FileHeader.NumberOfSections; 8 CopyMemory(&image_dos_header,pMem,sizeof(IMAGE_DOS_HEADER)); 9 ReservedHeaderSize = image_dos_header.e_lfanew - 10 sizeof(IMAGE_DOS_HEADER); 11 if((ReservedHeaderSize&0x80000000)==0x00000000) 12 { 13 CopyMemory(reservedheader, 14 pMem+ReservedHeaderRO, 15 ReservedHeaderSize); 16 } 17 CopyMemory(&image_nt_headers, 18 pMem+image_dos_header.e_lfanew, 19 sizeof(IMAGE_NT_HEADERS)); 20 dwRO_first_section = image_dos_header.e_lfanew + 21 sizeof(IMAGE_NT_HEADERS); 22 CopyMemory(&image_section_header, 23 pMem+dwRO_first_section, 24 SectionNum*sizeof(IMAGE_SECTION_HEADER)); 25 for(i=0;i< SectionNum;i++) 26 { 27 image_section[i] = (char*)GlobalAlloc( 28 GMEM_FIXED | GMEM_ZEROINIT, 29 PEAlign(image_section_header[i].SizeOfRawData, 30 PEfile.image_nt_headers.OptionalHeader.FileAlignment)); 31 CopyMemory(image_section[i], 32 pMem + image_section_header[i].PointerToRawData, 33 image_section_header[i].SizeOfRawData); 34 } 35 } 36 else//FALSE = data is being initialized 37 { 38 DWORD SectionNum = PEfile.image_nt_headers 39 .FileHeader.NumberOfSections; 40 CopyMemory(pMem, 41 &image_dos_header,sizeof(IMAGE_DOS_HEADER)); 42 ReservedHeaderSize=image_dos_header.e_lfanew - 43 sizeof(IMAGE_DOS_HEADER); 44 if((ReservedHeaderSize&0x80000000)==0x00000000) 45 { 46 CopyMemory(pMem + ReservedHeaderRO, 47 reservedheader, 48 ReservedHeaderSize); 49 } 50 CopyMemory(pMem+image_dos_header.e_lfanew, 51 &image_nt_headers, 52 sizeof(IMAGE_NT_HEADERS)); 53 dwRO_first_section = image_dos_header.e_lfanew + 54 sizeof(IMAGE_NT_HEADERS); 55 CopyMemory(pMem+dwRO_first_section, 56 &image_section_header, 57 SectionNum*sizeof(IMAGE_SECTION_HEADER)); 58 for(i=0;i< SectionNum;i++) 59 { 60 CopyMemory(pMem+image_section_header[i].PointerToRawData, 61 image_section[i], 62 image_section_header[i].SizeOfRawData); 63 } 64 } 65 }
PE unpack section need to import two essential API functions to load dynamically all other API functions in Run-time load. LoadLibraryA() and GetProcAddress() from Kernel32.dll are vital function to import other API function with some tricky methods. AssembleIT() is reserved to undertake this task. We should change the import table address and size for turning to new import table directory inside extra section. To retrieve the old import table, it has to reload and rebuild import table directory in the next step to perform program code. Loader section use LoadLibraryA() and GetProcAddress() to seek relative virtual address of importation function of dynamic link libraries.
Table-2
Import Table Address -> | "Kernel32.dll", 0x00 |
LoadLibrary_RVA | |
GetProcessAddres_RVA | |
0x00,0x00 | |
LoadLibrary_RVA -> | 0x00,0x00,"LoadLibraryA" |
GetProcessAddres_RVA -> | 0x00,0x00,"GetProcessAddress" |
It is important to protect import table directory from reverse engineering process. Danilo Bzdok has used technical methods to destroy import thunk data and crypt import information in [6] yoda's Crypter. This part is retrievable again by loader code section. I applied his methods in my PE Protector with bringing some part of code to C and remind other part in assembly. ''ProcessOrgIT()'' provide all we need to do our purpose. In loader section, ''INIT_IMPORT_TABLE()'' routine implements our point about API redirection and Import table rebuilt.
PE Protector should able to detect if program debugs and prevent from debugging. OllyDbg and SoftICE are two important debuggers that can bypass many tricks which cause to halt debuggers. However, I should introduce some simple methods to detect debuggers. I know all of these methods do not have any effect in recently additional plug-ins for mentioned debuggers.
1. IsDebuggerPresent Windows API: ''IsDebuggerPresent()'' will return none zero value whenever the current process is running in the context of a debugger.
1 if(CreateFile( "////.//NTICE", GENERIC_READ | GENERIC_WRITE, 2 FILE_SHARE_READ | FILE_SHARE_WRITE, 3 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 4 NULL)!=INVALID_HANDLE_VALUE) 5 { 6 There is SoftICE NT on your system; 7 } 8 9 10 if(CreateFile( "////.//SICE", GENERIC_READ | GENERIC_WRITE, 11 FILE_SHARE_READ | FILE_SHARE_WRITE, 12 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 13 NULL)!=INVALID_HANDLE_VALUE) 14 { 15 There is SoftICE98 on your system; 16 }
3. Probe Processes: Some time it needs to search for specific process or isolates to specially process. It was demonstrated how finding parent process and check if it is ''EXPLORER.EXE'' and killing all parent process except Explorer windows in sample source code. There are both C++ and assembly code inside ''CryptStuff.CPP'' to illuminate this task.
1 void GetFileNameFromPath(char* szSource) 2 { 3 char *szTemp=strrchr(szSource,'//'); 4 if(szTemp!=NULL) 5 { 6 szTemp++; 7 DWORD l=DWORD(strlen(szTemp))+1; 8 CopyMemory(szSource,szTemp,l); 9 } 10 } 11 void AntiDebug() 12 { 13 char lpszSystemInfo[MAX_PATH]; 14 HANDLE hSnapshot=NULL; 15 DWORD PID_child; 16 DWORD PID_parent,PID_explorer; 17 HANDLE hh_parnet = NULL; 18 PROCESSENTRY32 pe32 = {0}; 19 pe32.dwSize = sizeof(PROCESSENTRY32);//0x128; 20 PID_child=GetCurrentProcessId();//getpid(); 21 hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0); 22 if (Process32First(hSnapshot, &pe32)) 23 { 24 while (Process32Next(hSnapshot, &pe32)) 25 { 26 GetFileNameFromPath(pe32.szExeFile); 27 CharUpperBuff(pe32.szExeFile,strlen(pe32.szExeFile)); 28 if(strcmp(pe32.szExeFile,"EXPLORER.EXE")==0) 29 { 30 PID_explorer=pe32.th32ProcessID; 31 } 32 if(pe32.th32ProcessID==PID_child) 33 { 34 PID_parent=pe32.th32ParentProcessID; 35 } 36 } 37 } 38 if(PID_parent!=PID_explorer) 39 { 40 hh_parnet= OpenProcess(PROCESS_ALL_ACCESS, 41 TRUE, PID_parent); 42 TerminateProcess(hh_parnet, 0); 43 } 44 else 45 { 46 MODULEENTRY32 me32 = {0}; 47 me32.dwSize = sizeof(MODULEENTRY32); 48 hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 49 PID_explorer); 50 if (Module32First(hSnapshot, &me32)) 51 { 52 do 53 { 54 if(PID_explorer==me32.th32ProcessID) 55 { 56 GetWindowsDirectory(lpszSystemInfo, 57 MAX_PATH+1); 58 strcat(lpszSystemInfo,"//"); 59 strcat(lpszSystemInfo,"EXPLORER.EXE"); 60 CharUpperBuff(me32.szExePath, 61 strlen(me32.szExePath)); 62 if(strncmp(me32.szExePath, 63 lpszSystemInfo, 64 strlen(lpszSystemInfo))) 65 { 66 GetFileNameFromPath(me32.szExePath); 67 if(strcmp(me32.szExePath, 68 "EXPLORER.EXE")==0) 69 { 70 hh_parnet= 71 OpenProcess(PROCESS_ALL_ACCESS, 72 TRUE, PID_explorer); 73 TerminateProcess(hh_parnet, 0); 74 } 75 } 76 } 77 }while (Module32Next(hSnapshot, &me32)); 78 } 79 } 80 }
This code will not work under Windows NT 4.0 because of absence ''CreateToolhelp32Snapshot(), Process32First(), Process32Next(), Module32First(), Module32Next()''. But all of these API functions could build by undocumented API function from ''NTDLL.DLL''. You can take a look at [10] 'Windows NT (2000) Native API reference' by Gary Nebbett. Furthermore, I recommend using [11] Process Explorer by Mark Russinovich to explore all run processes in your systems. It will help to understand better them.
Some time you need to clean all unnecessary data such as debug information, relocation section and making small DOS header and remove MS-DOS stub Program. You should reserve these items in your PE Protector with some considerations. For instance, Relocation section do not have any effect in running EXE files but it plays an important role in OLE-Active Controls and Dynamic Link Libraries.
The project compiles with Visual C++ .net 2003 and doesn't require any spare tools. It works under all Windows version except Windows NT 4.0 and Windows 95.
This article and its source could be an introduction to PE protector tools and demonstrating how these tools works. I hope it covers the absence of this kind of interesting topics and tools in open source area.
[1] "Microsoft Portable Executable and Common Object File Format Specification", Microsoft Corporation, Revision 6.0, February 1999
[2] "Peering Inside the PE: A Tour of the Win32 Portable Executable File Format", Matt Pietrek, MSDN Library, March 1994
[3a] "An In-Depth Look into the Win32 Portable Executable File Format", part 1, Matt Pietrek, MSDN Magazine, February 2002
[3b] "An In-Depth Look into the Win32 Portable Executable File Format", part 2, Matt Pietrek, MSDN Magazine, March 2002
[4] PEview Version 0.67, Wayne J. Radburn
[5] MSDN Library, Microsoft Corporation, April 2003
[6] yoda's Crypter, Danilo Bzdok
[7] UPX, the Ultimate Packer for eXecutables, Markus F.X.J. Oberhumer & Laszlo Molnar
[8] LZO real-time data compression library, Markus F.X.J. Oberhumer
[9] aPLib compression library, Joergen Ibsen
[10] "Windows NT (2000) Native API reference", Gary Nebbett
[11] Process Explorer, Mark Russinovich